Why
src/main/wiring/codex.ts currently writes a [model_providers.<id>] block into ~/.codex/config.toml and leaves the credential entirely to env vars. The user either sets OPENAI_API_KEY in their shell (often forgotten) or duplicates it in ~/.codex/auth.json. The other harness writers (claudeCode, openCode) put the key in the config itself — Codex is the outlier.
We already have a working macOS Keychain path (cli/src/lib/vault.ts uses security add-generic-password) and the Electron app has safeStorage. The wiring layer can hand the API key to a writer that puts it into ~/.codex/auth.json as { "OPENAI_API_KEY": "<key>" }. Codex already reads from that file when OPENAI_API_KEY isn't in env.
Scope
- New writer
src/main/wiring/codexAuth.ts that reads ~/.codex/auth.json, merges { OPENAI_API_KEY: <key> }, and writes atomically (use writeJsonAtomic from fsutil.ts).
- Keep using chmod-0600 like the other writers. Skip the write on Windows or when the user hasn't enabled file storage (a runtime guard).
- Update
applyWiring in src/main/wiring/index.ts so Codex applies both writers (config.toml + auth.json).
- Mirrored change in
cli/src/lib/wiring.ts (still a thin re-export — no change needed; same provider).
- The CLI already lets the user pass
--api-key to hoist gateway use; the apply path automatically hands it to the auth writer now.
- IPC response shape: when both writers wrote, both results show up in
HarnessWiringResult[].
Acceptance criteria
References
src/main/wiring/codex.ts (current)
src/main/wiring/index.ts:9 (orchestrator)
- Codex
~/.codex/auth.json shape from https://github.com/openai/codex (config docs)
Why
src/main/wiring/codex.tscurrently writes a[model_providers.<id>]block into~/.codex/config.tomland leaves the credential entirely to env vars. The user either setsOPENAI_API_KEYin their shell (often forgotten) or duplicates it in~/.codex/auth.json. The other harness writers (claudeCode,openCode) put the key in the config itself — Codex is the outlier.We already have a working macOS Keychain path (
cli/src/lib/vault.tsusessecurity add-generic-password) and the Electron app hassafeStorage. The wiring layer can hand the API key to a writer that puts it into~/.codex/auth.jsonas{ "OPENAI_API_KEY": "<key>" }. Codex already reads from that file whenOPENAI_API_KEYisn't in env.Scope
src/main/wiring/codexAuth.tsthat reads~/.codex/auth.json, merges{ OPENAI_API_KEY: <key> }, and writes atomically (usewriteJsonAtomicfromfsutil.ts).applyWiringinsrc/main/wiring/index.tsso Codex applies both writers (config.toml + auth.json).cli/src/lib/wiring.ts(still a thin re-export — no change needed; same provider).--api-keytohoist gateway use; the apply path automatically hands it to the auth writer now.HarnessWiringResult[].Acceptance criteria
hoist gateway use openrouter --provider openai --api-key sk-xxx,~/.codex/auth.jsoncontains"OPENAI_API_KEY": "sk-xxx"with chmod 0600.~/.codex/config.tomlhas the[model_providers.openai]block andmodel/providerlines.codexfrom a fresh shell (noOPENAI_API_KEYin env) can still use the gateway.stat ~/.codex/auth.json) =0600.notesaying "Codex on this platform reads OPENAI_API_KEY from env; please set it in your shell" — no crash.auth.jsonare preserved.References
src/main/wiring/codex.ts(current)src/main/wiring/index.ts:9(orchestrator)~/.codex/auth.jsonshape from https://github.com/openai/codex (config docs)