Skip to content

[Bug][Windows][Codex App Beta]: zero-byte coordinator blocks sync and obscures Desktop picker diagnosis #2291

Closed

Description

@lidge-jun

Client or integration

Codex App

Area

Catalog / models

Summary

On Windows with the current Codex App Beta, a zero-byte native write-coordinator database caused every ocx sync / startup sync to regenerate the routed catalog but refuse the config injection:

A missing coordinator row cannot be initialized while native Codex routing residue exists.

The CLI left the user with a partially updated state: opencodex-catalog.json contained routed models, but config.toml had no model_catalog_json or openai_base_url, so the Beta app restarted onto the native OpenAI catalog. ocx doctor reported the app-server catalog as current and did not identify the rowless coordinator or provide a recovery command.

After moving the proven-empty coordinator file aside, ocx sync succeeded and wrote the two root config keys. The fresh cache then contained 12 rows, including five routed rows. The Beta Desktop picker still displayed only OpenAI models, which is a separate renderer allowlist/compatibility condition related to #241 and the explicit nativeAlias workaround from #1244. This report is primarily about the unrecoverable rowless coordinator state and missing diagnosis; the Beta picker behavior is included so the two failure layers are not conflated.

Expected behavior:

  • OpenCodex must not leave or reuse a zero-byte coordinator as permanent authority.
  • ocx doctor should identify an empty/unversioned/rowless coordinator and print a safe, actionable recovery.
  • A failed config injection should be surfaced prominently enough that a successful catalog write is not mistaken for a successful sync.
  • The Codex App Beta renderer allowlist should be named separately when the on-disk catalog/cache and app-server side are already correct.

Reproduction

  1. Use Codex App Beta on Windows with a running OpenCodex service and at least one routed provider.
  2. Reach a state where the effective coordinator path exists as a zero-byte file while ~/.codex/opencodex-catalog.json contains routed entries.
  3. Run ocx sync or restart the service.
  4. Observe that provider discovery/catalog generation succeeds, then config injection fails with the missing-coordinator/native-residue message.
  5. Restart the Codex app-server.
  6. Observe that the app rebuilds models_cache.json from the native OpenAI path because the root injection keys were not written.
  7. Move only the proven-zero-byte coordinator aside and rerun ocx sync.
  8. Observe successful injection and a deliberately stale 12-row models_cache.json containing five routed rows.
  9. Restart the Beta app-server. If no explicit nativeAlias combo is configured, the Desktop picker may still show only OpenAI models due to the separate renderer allowlist behavior tracked by [Bug]: Routed models are loaded by Codex app-server but missing from Desktop model picker #241/fix(codex): preserve routed models in desktop picker #1244.

Recovery performed on the affected device:

  • Verified coordinator: size 0, PRAGMA user_version = 0, no tables, no rows.
  • Verified residue: routed opencodex-catalog.json; integration record missing.
  • Moved the empty file to a same-directory .rowless-backup-20260821T2045 path; did not delete it.
  • Ran ocx sync: exit 0, config injection succeeded.
  • Ran ocx sync --restart-codex: the stale app-server and code-mode host stopped and respawned.
  • Verified fresh state: model_catalog_json and openai_base_url present; cache has 12 rows and five routed entries; proxy/service healthy.

Version

OpenCodex 2.27.0, dev checkout 3e130d239a98

Operating system

Windows 11 Insider build 26200; OpenAI.CodexBeta 26.727.4816.0 x64

Provider and model

Anthropic OAuth plus OpenCode Go routed models; not specific to one model

Logs or error output

+ 5 models appended to Codex catalog
Codex configuration was not written: A missing coordinator row cannot be initialized while native Codex routing residue exists.

# coordinator inspection before recovery
size: 0
PRAGMA user_version: 0
tables: []
rows: []
residue: catalog
integration record: missing

# after moving only the empty coordinator aside
+ 5 models appended to Codex catalog
Pointed Codex's built-in openai provider at the opencodex proxy (openai_base_url).
All models now route through opencodex proxy (like OpenRouter).

# post-recovery cache
fetched_at: 2000-01-01T00:00:00Z
client_version: 0.0.0
models: 12
routed models: 5

The repeated Removed unsupported reasoning efforts: max, ultra warning is a separate runtime skew: OpenCodex selected global codex.cmd 0.141.0 for catalog clamping while the Codex App Beta cache identified client 0.146.0. It did not cause the missing routed rows, but it adds misleading noise during recovery.

Screenshots and supporting files

No screenshot attached. Relevant related work:

Redacted configuration

{
  "defaultProvider": "openai",
  "providers": {
    "openai": { "authMode": "forward" },
    "anthropic": { "authMode": "oauth", "models": ["<redacted>"] },
    "opencode-go": { "baseUrl": "<redacted>" }
  },
  "combos": {}
}

Checks

  • I searched existing issues and documentation.
  • I removed secrets, tokens, account details, request credentials, and personal data.

Activity

  1. added
    bugSomething isn't working
    catalogModel catalog, slugs, visibility, routed entries
    cliCLI, config inject, packaging flags
    on Aug 21, 2026
  2. added
    platformOS/service/tray/ACL (Windows-heavy, not Windows-only)
    on Aug 21, 2026
  3. lidge-jun commented on Aug 21, 2026

    @lidge-jun
    OwnerAuthor

    Additional evidence from the affected Codex/ChatGPT Beta package after local recovery:

    • Package: OpenAI.CodexBeta 26.727.4816.0 on Windows 11 Insider build 26200.
    • Bundled runtime: codex-cli 0.146.0-alpha.9.2.
    • The repaired config and catalog are loaded by the bundled runtime.
    • A direct model/list request with includeHidden: true returns all 12 catalog rows.
    • All 5 non-OpenAI routed rows are present with hidden: false:
      • anthropic/claude-fable-5
      • anthropic/claude-opus-4-6
      • anthropic/claude-opus-5
      • opencode-go/muse-spark-1.2-contributor
      • opencode-go/ox-alpha-free
    • Inspection of the Beta renderer bundle shows its Codex model query calls list-models-for-host with includeHidden: true. The availability filter admits every row with hidden: false, and the Codex model menu maps the resulting models array directly into menu items.

    Therefore the OpenCodex catalog/config/app-server path is healthy after recovery, yet the user still reports an OpenAI-only picker. The remaining mismatch is downstream in the combined Beta renderer/surface state (or the user is being shown the ChatGPT cloud picker rather than the Codex task picker), not in catalog generation or model/list.

    No credentials, account identifiers, or local user paths were included in this diagnostic.

  4. Ingwannu commented on Aug 21, 2026

    @Ingwannu
    Owner

    Confirmed against current dev@3e130d239a986a1783ca484aec8d8ce7216ac66e.

    The report identifies a real split-state failure. codexWriteCoordinationEligibility() currently treats any existing coordinator pathname as authoritative, including a zero-byte file. The transaction opener then treats that zero-byte file like a newly created database, but initialization correctly refuses once routed native residue is present. As a result, the catalog can be refreshed while config injection is refused, and the next Codex App restart falls back to the native catalog.

    I will keep the fix scoped to the coordinator/injection boundary and diagnosis:

    • distinguish absent, zero-byte, unversioned, rowless, valid, unsafe, and unreadable coordinator states without creating or modifying SQLite sidecars;
    • make ocx doctor print the exact coordinator path, the observed state, and a recovery action instead of reporting only the app-server/catalog layer;
    • add an explicit recovery path that backs up only a still-identical, still-non-authoritative coordinator in the same directory, revalidates file identity and SQLite state immediately before mutation, and fails closed for symlinks/reparse points, foreign ownership, valid rows, changed files, or lock contention;
    • prevent a stable zero-byte coordinator from forcing a pre-substrate routed home into the coordinated initialization refusal, without auto-adopting legacy residue or weakening the existing rowless/unversioned refusal contract;
    • add regressions for zero-byte residue, valid coordinators, rowless/unversioned databases, identity substitution, and the doctor wording. The Desktop renderer allowlist/nativeAlias behavior remains separate under [Bug]: Routed models are loaded by Codex app-server but missing from Desktop model picker #241/fix(codex): preserve routed models in desktop picker #1244.

    I will open this as a Draft PR against dev; it will remain unmerged pending independent maintainer review and exact-head CI.

  5. lidge-jun commented on Aug 21, 2026

    @lidge-jun
    OwnerAuthor

    리뷰 · 우선순위 67 / 80

    지금 dev HEAD 3e130d239에서 이 재현이 코드랑 맞음. src/codex/inject-coordination.ts:50-60 codexWriteCoordinationEligibility가 existsSync(coordinatorPath)만 봄. 0바이트여도 파일이 있으면 { kind: "coordinated" }임. 그다음 락이 src/codex/transition-state.ts:349-426 openCodexCoordinatorTransaction을 탐. 여기선 이미 0바이트를 레이스 오프너로 보고 databaseWasEmpty = before.size === 0 (:369). initialize(..., databaseWasAbsent || databaseWasEmpty) (:426). 주석이 "Treat that exact state like ENOENT"임 (:364-368). 락 레이어는 빈 파일을 첫 사용으로 봄. eligibility는 존재만으로 권위 있는 코디네이터로 봄. 이 두 문이 충돌함.

    충돌이 initialize (:283-306)에서 터짐. 빈 파일+absent 취급이면 테이블 만들고 행이 없음. !existing이면 assertInitialStateCanBeCreated() (:269-281). classifyNativeRoutedResidue().kind !== "clean"이면 그 메시지 그대로 던짐 (:276-279). 리포터 로그랑 동일함. 카탈로그 재생성은 코디네이터 행 생성 전에 이미 됨. 주입만 거절. opencodex-catalog.json엔 라우티드 모델 있고 config.toml엔 model_catalog_json/openai_base_url 없음. 부분 성공이 버그임. 의도된 #1049 거절을 0바이트 껍데기에 적용한 거임.

    왜 치우면 됨. 파일을 같은 디렉터리로 옮기면 existsSync가 false. eligibility가 코디네이터 없음으로 가고 (:61-89), residue면 { kind: "legacy-uncoordinated" } (:84-89). 프리-서브스트레이트 주입 경로. 코디네이터 행을 안 만듦. assertInitialStateCanBeCreated를 안 탐. 그래서 두 번째 ocx sync가 주입에 성공함. 복구가 매직이 아님. 0바이트를 "있음=권위"로 분류한 게이트를 우회한 거임. 비전공자가 PRAGMA user_version 할 일이 아님.

    테스트가 이 조합을 안 잠금. tests/codex-transition-state-first-use-regression.test.ts:54-67은 깨끗한 tmp 홈에서 0바이트 = first-use만 봄. residue 없음. :69-79 비어 있지 않은 unversioned는 거절. 그건 #1049 계약임. 유지해야 함. 깨진 건 "0바이트 + 라우티드 카탈로그 residue + existsSync". eligibility 테스트가 tests 트리에 없음. inject-coordination.ts:58-60 주석이 "unversioned and rowless cases it must refuse rather than adopt"인데 0바이트는 rowless가 아님. 스키마도 행도 없는 껍데기임. 비어 있지 않은 unversioned랑 묶지 말 것.

    ocx doctor가 이 층을 안 봄. src/cli/doctor.ts:991-1007은 히스토리 코디네이터 네임스페이스만 프로브함. missing이면 "not created yet". 쓰기 코디네이터 파일 크기/user_version/테이블/행은 없음. :1059-1066 collectCodexAppServerCatalogState는 앱-서버 vs 온디스크 카탈로그임. 주입 키 유무가 아님. 카탈로그 JSON만 맞고 루트 키가 없어도 [OK] current가 찍힘. 리포터가 본 그거임. #241/#1244 픽커 얼라우리스트는 주입 성공 다음 레이어임. 이 이슈에 nativeAlias 워크어라운드 넣지 말 것. 섞으면 진단이 또 죽음.

    #2188 L1–L9 사이드카 이미 dev. x_search 넣지 말 것. Grok OAuth Chat 기본(#2255)/GUI 옵트인 Responses(#2266)/#2283 웹서치 필드랑 다른 레인임. types.ts/config.ts 스플릿 안 씹힘. 스키마 PR 아님. 프리뷰 배포 아님. v2.29.0 이미 태그됨. 헤드 3e130d239는 카탈로그 리프레시 데브로그뿐임. 핫 크래시는 아닌데 Windows Codex App Beta가 네이티브 OpenAI 카탈로그로 조용히 떨어짐. 닥터가 OK. 그래서 67.

    해결방안: 입증된 빈 코디네이터(size 0, user_version 0, 테이블 없음)는 eligibility에서 없는 파일로 취급함. residue면 legacy-uncoordinated 주입. 깨끗하면 first-use 행 생성. existsSync만으로 coordinated 주지 말 것. 비어 있지 않은 unversioned/#1049 거절은 유지. ocx doctor는 쓰기 코디네이터 경로·크기·버전·테이블·행을 찍고, 빈 파일이면 삭제하지 말고 같은 디렉터리로 옮긴 다음 ocx sync라고 말함. sync 출력은 카탈로그 성공이랑 주입 실패를 한 줄로 뭉개지 말 것. 픽커 얼라우리스트는 #241/#1244에 남겨. 스플릿이 inject-coordination.ts를 옮기면 리베이스하지 말고 닫고 다시 짜라. 지금은 그 정도 아님.

    이 댓글은 grok-bot이 작성했습니다

  6. Ingwannu commented on Aug 21, 2026

    @Ingwannu
    Owner

    Draft PR #2295 is now open against dev at exact head 6d5f0cf2c.

    The implementation includes the owner/Grok review boundary rather than treating size === 0 alone as sufficient:

    • a fresh zero-byte pathname remains on the coordinated path, so a live SQLite creator cannot have its lock bypassed;
    • only a file settled beyond the creation window, unchanged by identity checks, and immutably observed as user_version = 0 with no tables may use the legacy-uncoordinated boundary;
    • non-empty unversioned and rowless databases remain fail-closed;
    • recovery is explicit, same-directory, evidence-preserving, and refuses active writers, sidecars, unsafe paths, changed identity, and valid/unknown databases.

    Validation on the PR head: 55 focused tests passed, typecheck passed, privacy scan passed, docs built 393 pages, and git diff --check passed. The unrelated service-token fixture failure noted in the PR body also reproduces on clean origin/dev@c0cbe494e in this runner.

    I requested independent review from @lidge-jun and will not self-approve or merge this PR.

  7. Ingwannu commented on Aug 21, 2026

    @Ingwannu
    Owner

    PR #2295 has been updated at a813019c98e8f31513e89a6666ee430d490b9840 to fix the false CI exit-1 result. The former failing Linux 2/4 shard now completes all 221 selected files in 19 batches with exit 0; focused tests, typecheck, and privacy scan also pass. The functional zero-byte coordinator fix is unchanged. This stays open pending exact-head CI and independent maintainer approval.

  8. Ingwannu commented on Aug 21, 2026

    @Ingwannu
    Owner

    Draft PR #2295 is now fully green at exact head a813019c967335f43f42d85448bec20c1abf3814, including the rerun enforce-target gate. The issue remains open pending independent maintainer approval; I did not self-approve or merge.

  9. lidge-jun commented on Aug 21, 2026

    @lidge-jun
    OwnerAuthor

    Fixed by #2295, landed on dev via merge train PR #2315 (merge commit ffa6d46). Proven zero-byte coordinator remnants are now routed to the absent-file boundary instead of wedging sync.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingcatalogModel catalog, slugs, visibility, routed entriescliCLI, config inject, packaging flagsplatformOS/service/tray/ACL (Windows-heavy, not Windows-only)

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions