Skip to content

ocx sync and restore back fail permanently with an unnamed config issue, leaving the Codex catalog truncated and routing un-injected #1453

Closed

Description

@MarcusNeufeldt

Client or integration

Codex CLI

Area

CLI

Summary

ocx sync and ocx restore back fail permanently on my install with a message that names no reason:

Codex sync did not complete. Fix the reported Codex config issue and retry.
Plain `codex` was not switched back to opencodex. Fix the reported Codex config issue and retry.

No "Codex config issue" is ever printed — not on stdout, not on stderr, and ocx doctor reports every check ok. There is nothing to act on, so the instruction "fix the reported issue and retry" cannot be followed.

This has two compounding consequences, because of the ordering documented in src/cli/index.ts:

// Only warn/restart when a catalog or models_cache write actually happened. This is
// deliberately not an `else`: refreshCodexModelCatalog runs before injectCodexConfig,
// so a sync can fail (`ok: false`) after the catalog was already rewritten
  1. The model catalog is rewritten before the injection fails, so it is left truncated. My proxy serves 435 models (200 native, 157 openrouter/opencode-go, 43 Anthropic, 35 Google) but opencodex-catalog.json contains only 9, all OpenAI-family. The non-OpenAI models are reachable through the proxy but never appear in Codex.
  2. Routing is never restored. ocx stop and ocx update both remove the injected openai_base_url and model_catalog_json root keys. Since sync / restore back can never complete, nothing puts them back. After every update I have to re-add them by hand from <CODEX_HOME>/opencodex.config.toml.

Expected: either the sync succeeds, or the specific config problem is printed so it can be fixed. Ideally the catalog would also not be rewritten until the injection is known to succeed.

Reproduction

100% reproducible on my install, across ocx update, full restarts of the proxy, and two different versions (2.11.0 and 2.12.0).

  1. Have opencodex injected into Codex (openai_base_url + model_catalog_json present in <CODEX_HOME>/config.toml).
  2. Run ocx stop (or ocx update, which stops as its first step). The two injected root keys are removed.
  3. Start the proxy again and confirm it is healthy (/healthz → 200, /v1/models → full list).
  4. Run ocx restore back → fails with the message above.
  5. Run ocx sync → fails with the message above.
  6. Inspect opencodex-catalog.json → rewritten, but containing only the OpenAI-family models.

The proxy is healthy throughout. The only way I have found to restore routing is editing config.toml manually using the keys documented in <CODEX_HOME>/opencodex.config.toml.

Things I ruled out

To save triage time, I checked the obvious candidates and none of them apply:

  • Config is valid UTF-8 — strict TextDecoder('utf-8', {fatal: true}) decode succeeds.
  • No duplicate TOML tables — 185 [projects.*] entries, zero duplicate keys.
  • Size is modest — 33,524 bytes.
  • Proxy is healthy — /healthz 200, /v1/models returns 435 entries, live requests succeed.
  • ocx doctor is entirely clean — every check ok, including "Codex restart safety", "Project Codex configs", and "OAuth reliability".
  • Not the stripped keys themselves — the failure persists identically after routing is manually restored and verified working end-to-end.

Not a duplicate of the nearby catalog issues

Suggested fixes

  1. Print the actual reason. syncModelsToCodex returns ok: false but the caller only prints a generic sentence referring to a "reported" issue that is never reported. Surfacing the underlying error would make this self-service.
  2. Do not rewrite the catalog before the injection is known to succeed, or roll the catalog back when injection fails — otherwise a failed sync actively degrades a working install by truncating the model list.
  3. Consider having ocx doctor exercise the same code path, since it currently reports a fully healthy system while sync cannot complete.

Version

2.12.0 (also reproduced on 2.11.0)

Operating system

Windows 11 Pro 10.0.26200

Provider and model

Proxy serves 435 models across openai (native), openrouter, opencode-go and anthropic; the Codex-facing catalog is reduced to 9 OpenAI-family entries.

Logs or error output

$ ocx sync
Codex sync did not complete. Fix the reported Codex config issue and retry.

$ ocx restore back
Plain `codex` was not switched back to opencodex. Fix the reported Codex config issue and retry.

$ ocx sync-cache
WARNING: 4 Codex app-server process(es) still running (PIDs: ...). Disk catalog/cache were
updated, but Codex may keep showing the old model list until those processes restart.
# catalog still contains 9 models afterwards

$ ocx doctor
# every section reports ok, including:
#   Codex restart safety      ok
#   Project Codex configs     ok     no project-local provider bypass detected
#   OAuth reliability         [OK] x3

# proxy is healthy and multi-provider the whole time:
GET /healthz      -> 200
GET /v1/models    -> 435 models
   {"openai-native":200,"opencode-go/openrouter-other":157,"anthropic":43,"google":35}

# but the Codex-facing catalog:
<CODEX_HOME>/opencodex-catalog.json -> {"models": [ ...9 entries, all OpenAI-family... ]}

Screenshots and supporting files

No response

Redacted configuration

{
  "providers": {
    "openai":  { "adapter": "openai-responses", "codexAccountMode": "direct", "authMode": "forward" },
    "openrouter": { "adapter": "openai-chat", "baseUrl": "https://openrouter.ai/api/v1" },
    "opencode-go": { "adapter": "openai-chat", "baseUrl": "https://opencode.ai/zen/go/v1" },
    "anthropic": { "adapter": "anthropic", "authMode": "oauth" }
  },
  "defaultProvider": "openrouter",
  "activeCodexAccountId": "__main__"
}

<CODEX_HOME>/config.toml is 33 KB with 185 [projects.*] trust entries, valid UTF-8, no duplicate keys.

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 11, 2026
  2. flyingsquirrel0419 commented on Aug 11, 2026

    @flyingsquirrel0419

    Confirmed against current dev (0c170525c). The destructive ordering is real: syncModelsToCodex() can commit catalog/cache changes before injectCodexConfig() returns a refusal, and an unsuccessful injection message is currently sent through the normal log() channel while the CLI later prints only the generic failure sentence.

    I cannot reproduce the reporter's exact host-specific refusal without the original config.toml, so I will not claim that specific trigger yet. I will fix the confirmed boundary narrowly:

    1. run a non-writing injection preflight through the same config transformation and ownership checks before provider discovery or any catalog/cache write;
    2. return immediately when that preflight refuses, preserving the existing catalog and models cache byte-for-byte;
    3. emit unsuccessful injection messages on stderr, including the concrete refusal and target Codex home, before the existing high-level CLI failure summary;
    4. add regressions proving an ambiguous/refused config never calls catalog refresh, the actionable reason reaches stderr for both ocx sync and ocx restore back, and seeded catalog/cache artifacts are not degraded.

    I will keep provider discovery and successful sync behavior unchanged, and I will link a dev-targeted draft PR with exact-head verification once the patch is ready.

  3. Ingwannu commented on Aug 11, 2026

    @Ingwannu
    Owner

    Implemented the confirmed failure boundary in draft PR #1461, targeting dev.

    Current head: 9e5cc13f53f9cf162b0cf7ce395261462f914273

    The patch now runs the real injection transformation and coordination-eligibility checks in a no-write preflight before provider discovery or catalog/cache replacement. A deterministic refusal therefore returns before catalog refresh, preserves existing catalog/cache bytes, and prints the concrete refusal on stderr. The normal injector still revalidates at the final write boundary, so the preflight is not treated as a reusable authorization result.

    Verification on latest dev:

    • focused regressions: 16 passed
    • typecheck: passed
    • privacy scan: passed
    • docs build: 221 pages
    • full suite: 10,930 passed, 11 skipped, 1 failed
    • the sole full-suite failure is the existing tests/codex-shim.test.ts service-token fixture mismatch; a clean latest-dev worktree reproduces the same failure independently (34 passed, 1 failed)

    The issue remains open pending independent review and integration. Review was requested from @lidge-jun and @Wibias.

  4. added 2 commits that reference this issue on Aug 11, 2026
  5. lidge-jun commented on Aug 11, 2026

    @lidge-jun
    Owner

    Fixed on dev by #1461 (merge 849ab5e35).

    ocx sync and ocx restore back now run the injection transformation and coordination-eligibility checks in a non-writing preflight before provider discovery or any catalog/cache replacement, and the concrete refusal goes to stderr instead of being swallowed by the generic summary. That closes both halves of this report: the message names a reason you can act on, and a deterministic refusal can no longer leave the catalog truncated behind it.

    Verified on the merged tree: tests/cli-restore-back.test.ts and tests/codex-sync-api.test.ts 16 pass / 0 fail.

    PRs here target dev, so GitHub does not auto-close linked issues; closing manually.

  6. MarcusNeufeldt commented on Aug 12, 2026

    @MarcusNeufeldt
    Author

    Root cause found on my host, and it turns out to be residue from #1454 — the two issues were not independent.

    What was blocking the sync

    A failed ocx service install (the one reported in #1454) died at the UAC gate after writing its canonical service assets. Those assets were never cleaned up, and they sat in <OPENCODEX_HOME> for six days:

    opencodex-service-task.xml       2792b   2026-08-08T05:17:53
    opencodex-service-launcher.vbs    494b   2026-08-08T05:17:53
    opencodex-service.cmd            3068b   2026-08-08T05:17:53
    service.log / service-state.json  ABSENT      <- install never completed
    

    Every ocx sync and ocx restore back from that moment on refused. Moving just those three files aside made the very next ocx sync succeed immediately:

    + 432 models appended to Codex catalog
    Pointed Codex's built-in openai provider at the opencodex proxy (openai_base_url).
    

    The Codex-facing catalog went from 9 entries to 439 (202 openai / 159 opencode-go+openrouter / 43 anthropic / 35 google), the routing keys were re-injected automatically, and <CODEX_HOME>/opencodex.config.toml — which had also gone missing — was regenerated. Nothing else on the host changed.

    Why 2.13.0 still printed nothing

    The fix in #1461 instruments the injection preflight, which is the right fix for that path. But my refusal was taking the earlier branch in syncModelsToCodex, which returns before the preflight and emits nothing:

    const admission = (deps.admitCodexWrite ?? admitCodexWrite)();
    if (admission.kind === "refused" && admission.authority === "service-home") {
      return { status: "refused", authority: "service-home", ok: false, ... };  // no log.error
    }
    ...
    const preflight = await deps.injectCodexConfig(p, config, { validateOnly: true });
    if (!preflight.success) {
      log?.error(preflight.message);   // instrumented
    }

    So on 2.13.0 the CLI still printed only Codex sync did not complete. Fix the reported Codex config issue and retry. with no reason on stdout or stderr. admitCodexWrite has four authorities (config | generation | service-home | external-provider); as far as I can tell only the preflight path reports.

    What made this hard to find from outside

    Throughout the six days, every diagnostic reported healthy:

    • ocx service status → service not installed (Task Scheduler)
    • ocx doctor → every section ok, including "Codex restart safety" and "Project Codex configs"
    • proxy /healthz → 200, /v1/models → 435 models, live requests succeeded

    Nothing surfaced that a write veto was in force, or that inert files from an aborted install were the reason.

    Suggestions

    1. Report the service-home refusal too (and the other admission authorities), not just the injection preflight. A one-line log?.error(admission.message) on that branch would have made this self-diagnosable on day one — CodexAdmission already carries a message.
    2. Have a failed service install clean up its own assets. Strictly this belongs to Windows: failed ocx service install leaves proxy stopped and Codex routing un-injected because teardown runs before the elevation check #1454, but the consequence lands here: an aborted install leaves a permanent, invisible veto on all Codex config writes.
    3. Consider surfacing an active write veto in ocx doctor, which currently reports a fully healthy system while every sync is being refused.

    Environment: reproduced continuously on 2.11.0, 2.12.0 and 2.13.0; Windows 11; single Codex account; codexAccountMode: direct. Happy to provide anything further from the host if useful.

  7. added 2 commits that reference this issue on Sep 17, 2026
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 flags

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions