Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
6625a3b
docs(plan): open the PR merge round with a full 43-PR disposition
lidge-jun Jul 31, 2026
bfa1a59
fix(cli): exit ocx init cleanly on piped stdin EOF (#754)
Wibias Jul 31, 2026
5d7ab0c
feat(api): serve the generated Codex catalog at GET /api/catalog (#709)
Wibias Jul 31, 2026
f726100
feat(claude): opt out of native/* models in the Claude Desktop list (…
Wibias Jul 31, 2026
0856b0c
feat(providers): add Baseten to the free provider directory
ahmetb Jul 31, 2026
ba77eb8
fix(kiro): resolve the Windows kiro-cli executable without PATH
aljjang95 Jul 31, 2026
decb08b
fix(anthropic): normalize the messages baseUrl and harden stream quir…
Wibias Jul 31, 2026
05d9c48
fix(anthropic): validate streamed tool arguments the same way as non-…
lidge-jun Jul 31, 2026
299cd2f
fix(anthropic): keep tool argument deltas incremental and fail malfor…
lidge-jun Jul 31, 2026
95d8ed7
revert(anthropic): drop the streamed tool-argument validation, keep t…
lidge-jun Jul 31, 2026
571f8a4
fix(test): queue full-suite runs instead of warning about the contention
lidge-jun Jul 31, 2026
c943955
docs(plan): record why the suite moved to macmini-cf
lidge-jun Jul 31, 2026
fd92e99
docs(plan): scope the log-timezone fix to a no-layout-change surface
lidge-jun Jul 31, 2026
a6d472e
fix(gui): render dashboard log timestamps in the server's timezone (#…
lidge-jun Jul 31, 2026
d3abf43
fix(providers): give Volcengine Ark non-empty assistant text on tool …
lidge-jun Jul 31, 2026
38f711b
fix(openai-chat): fail a truncated tool call before it is flushed as …
lidge-jun Jul 31, 2026
2eebd92
fix(providers): send Ark the structured content form, not a placehold…
lidge-jun Jul 31, 2026
c4acc2e
docs(providers): label the Ark content shape as unverified in the cod…
lidge-jun Jul 31, 2026
3f7a504
fix(providers): honour the registry private-network default at the fe…
lidge-jun Jul 31, 2026
13a76a3
fix(tray): keep the Windows autostart Run entry under 260 characters …
Wibias Jul 31, 2026
2d0c6a9
fix(service): verify the proxy is really gone before reporting a stop…
lidge-jun Jul 31, 2026
5530b8c
fix(service): only pay the restart-window wait where a supervisor can…
lidge-jun Jul 31, 2026
c3424e9
test(service): pin the platform default, not just the explicit flag
lidge-jun Jul 31, 2026
d380b1b
docs(plan): record the round outcome, including what was wrong
lidge-jun Jul 31, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
177 changes: 177 additions & 0 deletions devlog/_plan/260731_pr_merge_round/000_plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
# 000 — 열린 PR 43건 머지 판단

로컬 `dev`는 성능 최적화 유닛이 진행 중이라 손댈 수 없다. 그래서 이번 라운드는
`dev`에 직접 올리지 않고 **스테이징 브랜치**에 쌓는다. 최적화 작업이 끝나면
사용자가 이 브랜치를 `dev`로 머지하고 릴리스한다.

- 워크트리: `/Users/jun/.codex/worktrees/260731-merge/opencodex`
- 브랜치: `codex/260731-pr-merge-round`
- 기준: `origin/dev = 356924263` (2026-07-31 재측정)
- 금지: `dev` / `main` / `preview` 푸시, npm 배포, 버전 bump

메인 체크아웃(`/Users/jun/Developer/new/700_projects/opencodex`)에는 커밋되지 않은
파일 22개가 있다. 이 라운드의 실패 조건 1번은 그 파일들이 스테이징되거나 사라지는
것이다. 배치마다 `git status --porcelain`으로 확인한다.

## 이 저장소의 머지 컨벤션 (이력에서 추출)

세 가지 형태가 실제로 쓰이고 있다. 커밋 이력에서 직접 확인한 것만 적는다.

| 형태 | 예시 | 언제 쓰이나 |
|---|---|---|
| GitHub 기본 머지 커밋 | `Merge pull request #773 from Wibias/fix/735-openai-chat-eof` (`5718d44e1`) | 웹 UI에서 머지할 때 |
| 손으로 쓴 머지 커밋 | `merge: PR #737 — tolerate late proxy readiness (#720)` (`62e937614`) | 메인테이너가 CLI로 머지할 때. 이슈 번호를 괄호로 단다 |
| 스쿼시 | `fix(cursor): add kimi-k3 with low/high/max effort tiers (#646)` (`275345a61`) | 기여자 PR을 한 커밋으로 접을 때 |

스쿼시가 지배적이다. 최근 300커밋 중 머지 커밋은 25개이고 나머지 275개는 전부
`type(scope): subject` 단일 부모 커밋이다.

**기여자 크레딧**: 스쿼시할 때 원저자를 `--author`로 유지하거나 `Co-authored-by`
트레일러를 단다. 최근 300커밋에 23건 있다(본문 기준 22건 + 머지 커밋 1건).
`275345a61`은 기여자를 author로 두고
메인테이너를 `Co-authored-by`에 넣었다 — 기여자 PR 위에 수정을 얹은 경우다.
`d24c5233f`는 반대로 메인테이너가 author, 기여자가 `Co-authored-by`다.
이번 라운드는 후자를 쓴다. 브랜치에서 재작성하는 건이 많기 때문이다.

**커밋 메시지 본문**: 이 저장소는 본문을 길게 쓴다. 무엇이 왜 깨졌는지, 어떤
측정을 했는지, 무엇이 반증됐는지까지 적는다(`1a46299b5`, `c777c8e76` 참고).
한 줄 요약만 있는 커밋은 이 저장소의 관행이 아니다.

**CI 게이트** (`AGENTS.md` + `MAINTAINERS.md`):

- PR 타깃은 `dev`만. `enforce-target`이 `main` 조상 위에 있으면서 `dev`보다 한참
뒤처진 PR과 빈약한 설명을 거부한다.
- 인증/자격증명/OAuth/Actions/릴리스 자동화/의존성 설치를 건드리면 **명시적 보안
리뷰**가 필요하다.
- 새 provider preset은 자격증명 목적지 변경이다. 문서화된 엔드포인트, ToS와 법인,
중개업체면 재판매·라우팅 권한, 유지보수 담당자, 인용 가능한 검증 날짜가 있어야
canonical 등록이 된다. 증거가 부족하면 `free-directory.ts`의 비활성 행으로.
- `bun run typecheck`, `bun run test`, `bun run privacy:scan`이 게이트다.
Comment on lines +40 to +49

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

printf '%s\n' '--- candidate files ---'
git ls-files \
  'devlog/_plan/260731_pr_merge_round/000_plan.md' \
  'devlog/_plan/260731_pr_merge_round/020_batch_a_restack.md' \
  'AGENTS.md' 'MAINTAINERS.md' 'package.json' 'bun.lockb' 'bun.lock' \
  | sort

printf '%s\n' '--- central plan ---'
cat -n devlog/_plan/260731_pr_merge_round/000_plan.md | sed -n '1,90p'

printf '%s\n' '--- batch checklist ---'
cat -n devlog/_plan/260731_pr_merge_round/020_batch_a_restack.md | sed -n '145,190p'

printf '%s\n' '--- repository guidance ---'
for f in AGENTS.md MAINTAINERS.md package.json; do
  if [ -f "$f" ]; then
    printf '\n--- %s ---\n' "$f"
    cat -n "$f" | sed -n '1,240p'
  fi
done

printf '%s\n' '--- related command references ---'
rg -n --hidden -S \
  'lint:gui|privacy:scan|bun run typecheck|bun x tsc|bun run test|CI 게이트|merge.?gate|검증' \
  -g '!node_modules' -g '!dist' -g '!build' .

Repository: lidge-jun/opencodex

Length of output: 50376


🏁 Script executed:

for f in \
  devlog/_plan/260731_pr_merge_round/000_plan.md \
  devlog/_plan/260731_pr_merge_round/020_batch_a_restack.md \
  package.json \
  AGENTS.md \
  MAINTAINERS.md
do
  if [ -f "$f" ]; then
    printf '\n--- %s ---\n' "$f"
    case "$f" in
      *000_plan.md) sed -n '32,58p' "$f" | nl -ba -v32 ;;
      *020_batch_a_restack.md) sed -n '165,182p' "$f" | nl -ba -v165 ;;
      package.json) sed -n '1,180p' "$f" ;;
      *) sed -n '1,220p' "$f" ;;
    esac
  else
    printf '\n--- missing: %s ---\n' "$f"
  fi
done

printf '\n--- focused workflow and hook references ---\n'
rg -n -S \
  'typecheck|lint:gui|privacy:scan|bun run test|bun x tsc|prepush|pull_request|enforce-target' \
  .github package.json scripts 2>/dev/null \
  | head -n 240

Repository: lidge-jun/opencodex

Length of output: 24799


🏁 Script executed:

printf '%s\n' '--- central plan lines 35-55 ---'
cat -n devlog/_plan/260731_pr_merge_round/000_plan.md | sed -n '35,55p'

printf '%s\n' '--- batch checklist lines 165-182 ---'
cat -n devlog/_plan/260731_pr_merge_round/020_batch_a_restack.md | sed -n '165,182p'

printf '%s\n' '--- CI workflow relevant section ---'
cat -n .github/workflows/ci.yml | sed -n '1,125p'

printf '%s\n' '--- GUI workflow files and relevant commands ---'
find .github/workflows -maxdepth 1 -type f -print | sort
rg -n -C 4 -S 'lint:gui|bun run lint|bun run test|bun x tsc|typecheck|privacy:scan|doctor' \
  .github/workflows

printf '%s\n' '--- exact command definitions ---'
python3 - <<'PY'
import json
from pathlib import Path
scripts = json.loads(Path("package.json").read_text())["scripts"]
for name in ("typecheck", "test", "privacy:scan", "lint:gui", "prepush"):
    print(f"{name}: {scripts.get(name)}")
PY

Repository: lidge-jun/opencodex

Length of output: 13325


🏁 Script executed:

python3 - <<'PY'
from pathlib import Path
import json, re

pkg = json.loads(Path("package.json").read_text())
print("package scripts:")
for key in ("typecheck", "test", "privacy:scan", "lint:gui", "prepush"):
    print(f"  {key}: {pkg['scripts'].get(key)}")

print("\nroot test wrapper:")
test_script = Path("scripts/test.ts")
if test_script.exists():
    for i, line in enumerate(test_script.read_text().splitlines(), 1):
        if i <= 130:
            print(f"{i:4}: {line}")

print("\nplan command occurrences:")
for name in (
    "devlog/_plan/260731_pr_merge_round/000_plan.md",
    "devlog/_plan/260731_pr_merge_round/020_batch_a_restack.md",
):
    text = Path(name).read_text()
    print(f"\n{name}")
    for i, line in enumerate(text.splitlines(), 1):
        if 35 <= i <= 60 or 165 <= i <= 182:
            if re.search(r"\bbun\b|lint|GUI|게이트|검증|CI", line, re.I):
                print(f"{i:4}: {line}")

print("\nCI run commands:")
ci = Path(".github/workflows/ci.yml").read_text().splitlines()
for i, line in enumerate(ci, 1):
    if 75 <= i <= 110:
        print(f"{i:4}: {line}")
PY

Repository: lidge-jun/opencodex

Length of output: 7701


Align the merge-gate definitions with CI.

000_plan.md:40-49 lists only three gates, but .github/workflows/ci.yml:81-107 also requires GUI tests, release-helper syntax, GUI lint, GUI build, and CLI help. 020_batch_a_restack.md:171-175 omits these checks. Define the complete gate once, then reference it from both plan documents. bun x tsc --noEmit is equivalent to bun run typecheck through package.json; changing that spelling alone does not fix the scope mismatch.

📍 Affects 2 files
  • devlog/_plan/260731_pr_merge_round/000_plan.md#L40-L49 (this comment)
  • devlog/_plan/260731_pr_merge_round/020_batch_a_restack.md#L171-L175
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@devlog/_plan/260731_pr_merge_round/000_plan.md` around lines 40 - 49, Define
one complete CI gate covering typecheck, unit tests, privacy scan, GUI tests,
release-helper syntax, GUI lint, GUI build, and CLI help, including the existing
security-review requirements where applicable. Update
devlog/_plan/260731_pr_merge_round/000_plan.md:40-49 to contain or reference
this canonical definition, and update
devlog/_plan/260731_pr_merge_round/020_batch_a_restack.md:171-175 to reference
the same definition; do not only rename the typecheck command.


## 판정 매트릭스

terra 서브에이전트 4개를 겹치지 않는 슬라이스로 병렬 파견해 각 PR을 **현재 HEAD
코드와 직접 대조**했다. PR 설명은 근거로 인정하지 않았고, `git cherry`와 patch-id로
이미 HEAD에 있는지부터 확인했다. 아래 SHA는 전부 내가 직접 재확인했다.

### 이미 dev에 들어간 것 — 닫는다 (7건)

같은 수정이 다른 커밋으로 이미 랜딩됐다. 지금 머지하면 **더 새로운 코드를 되돌린다.**

| PR | HEAD의 해당 커밋 | 이슈 |
|---|---|---|
| #736 Windows 서비스 상태 locale 독립 | `1d9e196e7` | #722 FULL |
| #752 tray host 소켓 상속 차단 | `c1ecbe1b5` | #733 FULL |
| #743 discovery 경로 하드닝 | `fd1933099` | #572 PARTIAL |
| #610 `codex --version` 프로브 캐싱 | `716f39cb6` | #606 FULL |
| #734 `OPENCODEX_BUN_PATH` 존중 | `9b5c864ff` + `f81e98aca` | #721 FULL |
| #777 catalog video modality | `e64a00e9f` (+ `7a041e2bc`, `299f35dc9`) | #759 PARTIAL |
| #533 npm 캐시 복구 | #557이 같은 14커밋 + 후속 2건 | — |

#736이 대표적인 함정이다. HEAD는 `decodeSchtasksOutput()`로 schtasks의 UTF-16LE
출력을 디코딩하는데(`src/service.ts:364-393`), PR head를 머지하면 그 블록이 통째로
삭제되고 `encoding: "utf8"`로 되돌아간다. "CLEAN하게 머지된다"가 "머지해도 된다"를
뜻하지 않는다는 걸 보여주는 사례다.

#734는 이전 라운드가 macOS `/var` vs `/private/var` 테스트 실패 때문에 뺐던
건인데, `f81e98aca`가 `realpathSync`로 그 테스트를 고쳤다. 이제 8/8 통과한다.

#533은 결함이 남아 있지만 #557이 같은 커밋 시리즈에 하드닝 2건을 더 얹은
후속이다. 둘 다 열어둘 이유가 없다.

### 이번 브랜치에 태울 것 (배치 A, 6건)

HEAD에 결함이 남아 있고, 보안 경계를 넓히지 않으며, 실패하는 회귀 테스트를
가져오는 건들이다. 전부 재기준(restack)이 필요하다 — 76커밋 이상 뒤처져 있다.

| PR | 내용 | 이슈 | 손봐야 할 것 |
|---|---|---|---|
| #772 | `GET /api/catalog` | #709 FULL | restack만 |
| #774 | `ocx init` 파이프 stdin EOF 무한루프 | #754 FULL | restack만 |
| #783 | Claude Desktop 모델 목록에서 `native/*` 제외 | #767 FULL | restack만 |
| #768 | Kiro Windows 실행파일 PATH 해석 | — | 디렉터리/비실행 파일 거부 추가 |
| #781 | Anthropic `/v1/messages` baseUrl 중복, 스트림 quirk | #765 PARTIAL | 문자열 `tool_use.input` 정규화, 죽은 `sawContent` 제거, **`/api/logs` 봉투 테스트 헬퍼 분리** |
| #769 | Baseten free-directory 행 | — | 없음 (canonical 아님) |

#772와 #783은 `src/codex/catalog.ts`에서 충돌한다. `readCatalog`와
`desktopVisibleNativeSlugs` export를 둘 다 살려야 한다.

**감사가 잡아낸 것 — #744를 배치 A에서 뺀다.** 이 PR은 OAuth 재조정을 바꾸고
provider 설정을 영속화하며 토큰 해석 순서를 static 분기 앞뒤로 옮긴다
(`59d95c0e4`, `39543a3c0`). `MAINTAINERS.md` 기준 명시적 보안 리뷰 대상이다.
"카탈로그를 static으로 고정한다"는 요약이 그 사실을 가렸다. 보류로 옮긴다.

**#781도 그대로 못 태운다.** 토픽 커밋 `70031f470`이 Anthropic 코드와 함께
`/api/logs` 테스트를 `logsFromApiBody`로 갈아끼운다. 그 헬퍼(`2f6c031cc`,
`tests/helpers/logs-api.ts`)는 배열과 `{logs}` 봉투를 **둘 다** 받아준다 — 배치 B가
거부하기로 한 바로 그 봉투 계약을 테스트 쪽에서 미리 받아들이는 것이다.
Anthropic 변경만 떼어내고 HEAD의 배열 단언은 그대로 둔다.

### 브랜치에서 다시 만들 것 (배치 B, 3건)

결함은 진짜인데 구현이 지금 트리와 맞지 않는다. 기여자 커밋을 그대로 태우면
회귀가 난다. `Co-authored-by`로 크레딧을 유지하며 재작성한다.

- **#790 / #784 — 대시보드 로그.** 둘 다 `/api/logs`를 배열에서
`{timeZone, logs}` 봉투로 바꾼다. 그 계약 변경이 배열을 가정한 기존 소비자를
깬다: `tests/server-auth.test.ts:1623`, `tests/claude-native-passthrough.test.ts:119`,
`tests/openai-provider-option-e2e.test.ts:489`, GUI mock 다수. 게다가 #790이
고친 유일한 테스트는 두 형태를 모두 허용해서 **패치 없이도 통과한다.**
배열 계약을 유지하고 타임존은 응답 헤더로 나르는 쪽으로 재작성한다.
#726(200건 상한)과 #725(타임존)를 함께 닫는다.
- **#771 — Windows autostart Run 260자 초과.** VBS 런처 방향은 맞다.
`tests/windows-tray.test.ts` import 충돌만 있고 나머지는 깨끗하다. #696 FULL.
- **#780 — Windows 스케줄러 stop.** 진단이 얕다. 패치는 `schtasks /end`가
**실패할 때만** 6.5초 기다린다. 그런데 보고된 실패는 `/end`가 성공했는데 래퍼가
살아남아 5초 뒤 자식을 재생성하는 경우다. 즉 이 패치로도 여전히 거짓 성공을
보고한다. 재시작 창을 통과할 때까지 검증하도록 다시 만든다. #764 PARTIAL.

### 보류 (증거·리뷰 대기)

**보안 리뷰가 필요한 건** — `MAINTAINERS.md`가 요구하는 명시적 보안 리뷰는
메인테이너 판단이다. 내가 대신할 수 없으므로 브랜치에 태우지 않고 근거만 남긴다.

- #782 admin token ACL opt-in. 그리고 버그가 하나 있다: 디렉터리 하드닝이 soft
continue할 수 있는데(`management-auth.ts:61-65`) 그 결과가 버려져서
`/api/settings`가 `aclUnverified: false`를 보고할 수 있다. 파일 하드닝만
상태를 세운다. 이건 리뷰 전에 고쳐야 한다.
- #744 Antigravity static 고정. OAuth 설정 영속화와 토큰 해석 순서 변경.
- #750 Codex 계정 풀 plan 영속화. 자격증명·토큰 회전 경합·계정 상태 영속화.
- #746 GitHub Copilot Responses 라우팅. OAuth 갱신과 키 풀 복구 경로.
- #644 Windows tray가 활성 Codex home을 따라가게. draft이고
`.github/workflows/pr-labeler.yml`까지 건드린다 — Actions 변경은 보안 리뷰 대상.
게다가 `.codexclaw/goalplans/**`와 `devlog/.DS_Store`가 diff에 들어 있다.
저장소 위생 문제라 그대로는 못 받는다.
- #779 TLS 종단 Origin scheme skew. 분석상 인증 우회는 없다 —
`requireManagementAuth`가 먼저 돌고(`index.ts:391`) 세션 경로는 여전히
origin 완전 일치와 CSRF를 요구한다(`management-auth.ts:205`). 그래도 CORS 수용
범위를 넓히는 변경이라 리뷰 대상이다.
- #775 Ollama private-network discovery (SSRF/destination policy).
- #778 doctor의 provider API key 진단 (자격증명 취급).
- #693 A6API 크레딧 (Bearer 키를 새 목적지 2곳으로 보낸다).
- #616 hosted image tool (management validation 변경).

**provider preset — 증거 미달**: #751(증거는 완비, CHANGES_REQUESTED 상태만 남음),
#747, #653, #611, #776. 각각 무엇이 빠졌는지는 `010`에 적는다.

**자체 리뷰 사이클이 필요한 대형 건**: #757(GPT-5.6 Pro 브라우저 자동화, 40파일),
#581(zh-TW 로케일, 59파일), #715(계정 풀 선택 순서, 62파일),
#707(외부 기여자의 보안 하드닝, 88파일), #671(exact account routing),
#569(readiness 계약, draft), #557(npm 캐시 복구, draft).
머지 라운드에서 처리할 물건이 아니다. 블라스트 반경과 리뷰 표면 때문이지
분량 때문이 아니다.

**기타**: #745(정규화는 맞는데 회귀 테스트가 없다 — 테스트를 우리가 쓴다),
#763(코드는 괜찮은데 필수 CI 기록이 없다), #793(#773이 왜 리버트됐는지 기록이
없다. 이유를 모른 채 같은 걸 되돌리는 건 안 된다 — 오너 판단 필요).

## 사이클 구성

- `010` — 이미 랜딩된 7건 PR과 해당 이슈 정리 (머지 없음)
- `020` — 배치 A: restack 6건
- `030` — 배치 B: 재작성 3건
- `040` — 남은 이슈 정리와 인계

각 배치는 `tsc` + 대상 테스트 + 배치 종료 시 전체 스위트 + `privacy:scan`을
통과한 뒤에만 푸시한다. 이슈는 랜딩된 코드가 실제로 결함을 없앤 게 확인될 때만
닫고, 부분 해결은 코멘트만 남기고 열어둔다.
91 changes: 91 additions & 0 deletions devlog/_plan/260731_pr_merge_round/010_superseded_closeout.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# 010 — 이미 랜딩된 7건 정리

머지가 없는 사이클이다. HEAD가 이미 같은 결함을 고쳤다는 걸 커밋 단위로 확인하고,
PR과 이슈를 근거와 함께 닫는다.

## 확인 방법

`git cherry HEAD refs/prs/<N>`으로 patch-id 동등성을 보고, 동등하지 않으면 HEAD
소스를 직접 읽어 결함이 사라졌는지 확인했다. patch-id가 다른데 결함은 없어진
경우가 있다 — 메인테이너가 같은 문제를 다르게 고친 경우다. 이때는 PR을 머지하면
**더 나은 구현을 되돌린다.**

## 건별 근거

### #736 → #722 (FULL)

HEAD `1d9e196e7 fix(service): make Windows status locale independent`.

`git cherry`가 PR의 커밋 하나를 `+`(미적용)로 표시하지만 오해다. HEAD의 구현이 더
새롭다. 확인:

```
git diff HEAD refs/prs/736 -- src/service.ts
```

PR head 방향으로 가면 `decodeSchtasksOutput()` 전체(`src/service.ts:364-393`)와
XML 판독 실패 분기(`:607-619`)가 **삭제되고** `runFile()`이 `encoding: "utf8"`로
돌아간다. schtasks `/query /xml`은 UTF-16LE를 뱉기 때문에 그 상태로는 모든 health
check가 실패하고 성공한 elevated create를 롤백한다. 즉 이 PR을 지금 머지하면
#722를 다시 연다.

### #752 → #733 (FULL)

HEAD `c1ecbe1b5 feat(windows): add restart-safe tray controls`.
`src/tray/windows.ts:474-477`이 `stdio: "ignore"`로 띄운다. 소켓 핸들이 상속되지
않는다. PR 커밋은 patch-id 동등(`-`).

### #743 → #572 (PARTIAL)

HEAD `fd1933099 fix(providers): harden discovery path and test isolation`.
`src/providers/model-discovery.ts:93-95`가 백슬래시와 `%2e` 디코딩된 `..` 세그먼트를
거부한다. 두 커밋 모두 patch-id 동등.

#572는 닫지 않는다. 그 이슈는 OpenAI 호환 provider 배치 승격 전체이고 이건 discovery
경로 하드닝 한 조각이다.

### #610 → #606 (FULL)

HEAD `716f39cb6 fix(codex): key catalog cache by runtime and address CodeRabbit follow-ups`.
`src/codex/catalog/bundled.ts:153-168`이 주입된 executor만 전달하고
`discoverAlternatives`를 기본 `false`로 둔다. 두 커밋 patch-id 동등.

이전 라운드 문서가 이 PR head를 NOT-ON-DEV로 기록했는데, 그 뒤에 랜딩됐다.

### #734 → #721 (FULL)

HEAD `9b5c864ff merge: PR #734` — 이미 머지돼 있다. 열려 있는 건 GitHub 상태가
갱신되지 않아서다. 남아 있던 macOS 테스트 실패는 `f81e98aca`가 고쳤다:
`tests/bun-runtime.test.ts:7-9`가 `realpathSync`로 temp root를 먼저 해석한다.
이전 라운드가 이 PR을 뺀 이유가 그 실패였다. 지금 8/8 통과.

### #777 → #759 (PARTIAL)

HEAD `e64a00e9f`가 `src/codex/catalog/parsing.ts:274-285`에서 모든 카탈로그 엔트리의
`input_modalities`를 정규화한다. `text`/`image`/`audio`만 통과시키고, 전부 걸러지면
빈 배열 대신 `["text"]`로 둔다. 오염된 영속 행 복구는
`tests/codex-catalog-sync-hardening.test.ts:161-193`이 덮는다. `7a041e2bc`와
`299f35dc9`가 커스텀 모델 라우트 쪽 enum 경계를 추가로 막았다.

PR의 테스트는 지금 컴파일도 안 된다 — import하는 sanitizer 헬퍼가 HEAD에 없다.
#759는 열어둔다. 이슈가 더 넓은 enum 경계 점검을 명시적으로 남겨뒀다.

### #533 → #557로 대체

patch-id로 확인: #533의 update-recovery 14커밋이 #557에 전부 같은 patch-id로
들어 있고 #557이 하드닝 2건을 더 얹었다. 다만 `refs/prs/533`이 `refs/prs/557`의
**그래프 조상은 아니다** — 재작성된 같은 시리즈다. 둘 다 draft이고 250커밋 이상
뒤처져 있다. #557을 후속으로 남기고 #533만 닫는다.

#737(`62e937614`, 이미 dev에 있음)이 이 둘을 대체하지 않는다는 것도 확인했다.
#737은 `src/update/job.ts`와 그 테스트 86줄만 바꾼다. 캐시 소유권 preflight,
recovery-tree 검증, 프로세스 트리 정리는 들어 있지 않다.

## 이슈 처리

닫는다: #722, #733, #606, #721.
열어둔다: #572(부분), #759(부분).

## 이 사이클이 바꾸는 파일

없다. 문서만 추가한다. GitHub 상태만 정리한다.
Loading
Loading