Client or integration
Codex CLI
Area
Service lifecycle
Summary
When any Codex thread in ~/.codex/state_*.sqlite is tagged model_provider='opencodex' on a paginated-history store — the schema every current Codex build uses — ocx restore, ocx stop, and ocx uninstall all refuse to touch ~/.codex/config.toml. The proxy can then be stopped and uninstalled while config.toml still contains model_provider = "opencodex" + [model_providers.opencodex] base_url = "http://127.0.0.1:10100/v1" (or the root openai_base_url override). Every subsequent codex invocation fails with a connection error on the dead port until the user hand-edits TOML.
There is no --force, no fallback strip, and no escape hatch in the CLI. The refusal message ("Native writer coordination is required") does not tell the user how to recover.
Expected instead:
One of:
- a documented
--force / "strip routing anyway" mode for restore/uninstall, accepting that opencodex-tagged threads will not resume;
- or, on refusal, a degraded-but-working end state: e.g. remove the root
model_provider/openai_base_url while keeping the [model_providers.opencodex] table on disk (so tagged threads still resolve by id and native requests fall back to the built-in openai provider);
- or at minimum, an actionable error telling the user the exact
config.toml lines to delete.
Notably, the apply path already implements the middle option: when the relabel unit stands down, injection deliberately keeps an existing opencodex table so tagged threads keep resolving (src/codex/inject.ts:497-502). The remove path has no equivalent.
Reproduction
- Loopback install,
codexDesktopAuthless: true, run ocx sync → config.toml gets model_provider = "opencodex" + the provider table.
- Start one Codex Desktop/CLI conversation → the thread row is written with
model_provider='opencodex' and history_mode='paginated' (current Codex schema).
ocx uninstall (or ocx stop, or ocx restore).
- Observe: the Codex-config step reports
Codex configuration preserved: history_paginated_requires_native_writer; uninstall finishes with a failed step; config.toml still routes to 127.0.0.1:10100.
codex / the desktop app → connection refused until manual TOML edit.
Version
2.56.0
Operating system
macOS 26 (arm64)
Checks
Why it happens
preflightCodexHistoryInjection is a read-only guard that returns "history_paginated_requires_native_writer" if the threads table has a history_mode column AND either the restore manifest is non-empty or any queried thread is paginated:
src/codex/history-provider.ts:404-421
if (!existsSync(resolvedPath)) {
return restoreEntries.length > 0 ? "history_state_database_missing" : null;
}
db = new Database(resolvedPath, { readonly: true });
const columns = db.query<{ name: string }, []>("PRAGMA table_info(threads)").all();
const paginatedColumn = columns.some(column => column.name === "history_mode");
if (paginatedColumn && restoreEntries.length > 0) return "history_paginated_requires_native_writer";
...
WHERE ${providerTableMode ? resumeHistory ? "model_provider IN ('openai', 'opencodex')" : "0"
: "model_provider = 'opencodex'"}
`).all();
for (const row of rows) {
if (paginatedColumn || row.history_mode === "paginated") return "history_paginated_requires_native_writer";
Both restore entry points refuse on that result, in order:
src/codex/inject/restore.ts:233-234 — config restore refuses before touching the journal:
const historyError = preflightCodexHistoryInjection(false, false);
if (historyError) return { state: "failed", changed: false, action: "failed", message: `Codex configuration and journal preserved: ${historyError}.` };
src/codex/inject/remove.ts:145-146 — the strip path refuses identically:
const historyError = preflightCodexHistoryInjection(false, false);
if (historyError) return { success: false, message: `Codex configuration preserved: ${historyError}. Native writer coordination is required.` };
Every user-facing command funnels into restoreNativeCodexAsync → restoreCodexConfigInlineImpl → that preflight: ocx restore (src/cli/dispatch.ts:196), ocx stop (src/server/stop-teardown.ts:60, src/cli/index.ts:793), ocx uninstall (src/cli/index.ts:1315), and the service paths (src/service/cli.ts:293,337).
ocx uninstall then completes the trap: the failed restore is recorded in failures (src/cli/index.ts:1364-1367 exits 1 and keeps config/backups "so the failed restore step can be retried"), but the proxy service/shim is still removed — so the state it preserves is precisely a config.toml pointing at a now-unroutable port.
Impact
- Self-inflicted deadlock: the guard exists to keep
opencodex-tagged threads resolvable, but after uninstall the endpoint is gone, so the protected threads fail at request time anyway — while all native use is also broken. The protection protects nothing once the proxy is uninstalled.
ocx status already knows this state is fatal ("Not running — Codex/Claude requests will fail with connection errors", src/cli/index.ts:1422-1423), yet restore/stop/uninstall can all produce it with no supported recovery path.
- Any authless / client-compaction / admission-token user on a current Codex build who has started ≥1 conversation is in the refusal class.
Suggested fix
Add a degraded mode to the restore preflight: when refusal is due only to history_paginated_requires_native_writer, allow stripping the root routing keys (model_provider, openai_base_url, experimental_realtime_ws_base_url) while leaving the marker-owned [model_providers.opencodex] table on disk, and print exactly what was kept and why. Offer ocx restore --force-remove-provider (or similar) for full removal including the table.
Related issues
This report is filed separately because it is not about how the refusal is classified or surfaced. It is about the end state after ocx uninstall has already completed: the guard preserves a config.toml pointing at a port that no longer exists, so the opencodex-tagged threads it protects fail at request time anyway, while all native Codex use breaks too. That recovery gap remains regardless of whether the warning is counted as fatal or advisory.
Investigated by reading the installed @bitkyc08/opencodex@2.56.0 package (npm -g; 2.56.0 is the current published version at time of filing). Every quoted snippet is byte-exact from that tree. Corroborating host state: loopback bind, codexDesktopAuthless: true, codexClientCompaction unset, no codexAccountNamespaces, 15-model injected catalog with no reserve row.
Client or integration
Codex CLI
Area
Service lifecycle
Summary
When any Codex thread in
~/.codex/state_*.sqliteis taggedmodel_provider='opencodex'on a paginated-history store — the schema every current Codex build uses —ocx restore,ocx stop, andocx uninstallall refuse to touch~/.codex/config.toml. The proxy can then be stopped and uninstalled whileconfig.tomlstill containsmodel_provider = "opencodex"+[model_providers.opencodex] base_url = "http://127.0.0.1:10100/v1"(or the rootopenai_base_urloverride). Every subsequentcodexinvocation fails with a connection error on the dead port until the user hand-edits TOML.There is no
--force, no fallback strip, and no escape hatch in the CLI. The refusal message ("Native writer coordination is required") does not tell the user how to recover.Expected instead:
One of:
--force/ "strip routing anyway" mode for restore/uninstall, accepting thatopencodex-tagged threads will not resume;model_provider/openai_base_urlwhile keeping the[model_providers.opencodex]table on disk (so tagged threads still resolve by id and native requests fall back to the built-inopenaiprovider);config.tomllines to delete.Notably, the apply path already implements the middle option: when the relabel unit stands down, injection deliberately keeps an existing
opencodextable so tagged threads keep resolving (src/codex/inject.ts:497-502). The remove path has no equivalent.Reproduction
codexDesktopAuthless: true, runocx sync→config.tomlgetsmodel_provider = "opencodex"+ the provider table.model_provider='opencodex'andhistory_mode='paginated'(current Codex schema).ocx uninstall(orocx stop, orocx restore).Codex configuration preserved: history_paginated_requires_native_writer; uninstall finishes with a failed step;config.tomlstill routes to127.0.0.1:10100.codex/ the desktop app → connection refused until manual TOML edit.Version
2.56.0
Operating system
macOS 26 (arm64)
Checks
Why it happens
preflightCodexHistoryInjectionis a read-only guard that returns"history_paginated_requires_native_writer"if the threads table has ahistory_modecolumn AND either the restore manifest is non-empty or any queried thread is paginated:src/codex/history-provider.ts:404-421Both restore entry points refuse on that result, in order:
src/codex/inject/restore.ts:233-234— config restore refuses before touching the journal:src/codex/inject/remove.ts:145-146— the strip path refuses identically:Every user-facing command funnels into
restoreNativeCodexAsync→restoreCodexConfigInlineImpl→ that preflight:ocx restore(src/cli/dispatch.ts:196),ocx stop(src/server/stop-teardown.ts:60,src/cli/index.ts:793),ocx uninstall(src/cli/index.ts:1315), and the service paths (src/service/cli.ts:293,337).ocx uninstallthen completes the trap: the failed restore is recorded infailures(src/cli/index.ts:1364-1367exits 1 and keeps config/backups "so the failed restore step can be retried"), but the proxy service/shim is still removed — so the state it preserves is precisely aconfig.tomlpointing at a now-unroutable port.Impact
opencodex-tagged threads resolvable, but after uninstall the endpoint is gone, so the protected threads fail at request time anyway — while all native use is also broken. The protection protects nothing once the proxy is uninstalled.ocx statusalready knows this state is fatal ("Not running — Codex/Claude requests will fail with connection errors",src/cli/index.ts:1422-1423), yet restore/stop/uninstall can all produce it with no supported recovery path.Suggested fix
Add a degraded mode to the restore preflight: when refusal is due only to
history_paginated_requires_native_writer, allow stripping the root routing keys (model_provider,openai_base_url,experimental_realtime_ws_base_url) while leaving the marker-owned[model_providers.opencodex]table on disk, and print exactly what was kept and why. Offerocx restore --force-remove-provider(or similar) for full removal including the table.Related issues
history_paginated_requires_native_writerguard, reported as a readyz classification problem and as anocx updateteardown abort.This report is filed separately because it is not about how the refusal is classified or surfaced. It is about the end state after
ocx uninstallhas already completed: the guard preserves aconfig.tomlpointing at a port that no longer exists, so theopencodex-tagged threads it protects fail at request time anyway, while all native Codex use breaks too. That recovery gap remains regardless of whether the warning is counted as fatal or advisory.Investigated by reading the installed
@bitkyc08/opencodex@2.56.0package (npm -g; 2.56.0 is the current published version at time of filing). Every quoted snippet is byte-exact from that tree. Corroborating host state: loopback bind,codexDesktopAuthless: true,codexClientCompactionunset, nocodexAccountNamespaces, 15-model injected catalog with no reserve row.