Problem
Codex sessions are billed at OpenAI API rates. The Settings page's "Codex token" is an OpenAI API key, forwarded to every codex exec turn as CODEX_API_KEY via scripts/in-workspace.sh. There is no way to use a ChatGPT subscription instead. Nothing on the box is logged in to Codex: the host has no Codex CLI, and each env's workspace/.codex/ holds only a config.toml (codex login status → "Not logged in").
Why it's not a one-liner like Claude
Claude auth is a single long-lived OAuth token (CLAUDE_CODE_OAUTH_TOKEN) passed as an env var. Codex has no equivalent. ChatGPT sign-in yields a token bundle in $CODEX_HOME/auth.json that Codex itself refreshes (after ~8 days, or on a 401) and writes back. OpenAI documents copying that file into containers / CI runners as supported, with one hard rule: one copy per serialized stream — never share a copy across concurrent jobs, because a refresh rotates the token and strands the other copies. Our fleet runs Codex turns in many envs concurrently, so the design has to respect that.
Docs: https://developers.openai.com/codex/auth (Login on headless devices) and https://developers.openai.com/codex/auth/ci-cd-auth (refresh + persistence rules).
Proposed design — server owns one canonical login and fans it out
- Log in once, on the box, into a server-owned dir (e.g.
data/codex/):
- Settings button "Sign in with ChatGPT": server runs
codex login --device-auth in a throwaway container from the existing workspace image with that dir mounted as CODEX_HOME, streams the URL + one-time code to the UI; operator approves in any browser. Codex 0.154 (current workspace image) supports this. Requires "device code login" enabled once in the operator's ChatGPT security settings.
- Fallback:
codex login on a laptop, paste the contents of ~/.codex/auth.json into a Settings textarea.
- Fan out per turn, host-side. Each env's workspace is a host bind mount, so before spawning a Codex turn the server copies the canonical
auth.json into workspace/.codex/, sets cli_auth_credentials_store = "file" in that env's config.toml, and does not export CODEX_API_KEY. No container changes, no rebuilds.
- Single writer keeps the canonical fresh. A weekly server job runs a one-line
codex exec in the throwaway container against the canonical dir, refreshing the bundle well inside the 8-day window. Envs always receive a fresh copy at turn start, so they never take the refresh path themselves → no concurrent rotation. Safety net: after a turn, copy an env's file back if its last_refresh is newer than the canonical's.
- Mode switch, not replacement. Settings gains "Codex auth: ChatGPT login / API key" with a status line (account, last refresh). API-key path stays.
AGENT_USAGE.md / MCP docs get a note.
Verify before building (each a 5-minute check in a workspace container)
Scope / effort
~1 day, all server-side: settings fields + two routes, a small helper for the throwaway container and file sync, the pre-turn copy in the agent spawn path (src/claude.js codex agent), Settings UI. Deploys with a server restart, no env rebuilds.
Caveats
auth.json holds live tokens → same secret handling as the API key (masked, redacted from logs, never in transcripts).
- Subscription usage windows apply instead of pay-as-you-go.
- OpenAI labels this an advanced flow and recommends API keys for automation; if refresh behaviour changes, the recovery path is "sign in again from Settings".
🤖 Generated with Claude Code
https://claude.ai/code/session_018puUXTJeSWdbG8ZMCFVqQL
Problem
Codex sessions are billed at OpenAI API rates. The Settings page's "Codex token" is an OpenAI API key, forwarded to every
codex execturn asCODEX_API_KEYviascripts/in-workspace.sh. There is no way to use a ChatGPT subscription instead. Nothing on the box is logged in to Codex: the host has no Codex CLI, and each env'sworkspace/.codex/holds only aconfig.toml(codex login status→ "Not logged in").Why it's not a one-liner like Claude
Claude auth is a single long-lived OAuth token (
CLAUDE_CODE_OAUTH_TOKEN) passed as an env var. Codex has no equivalent. ChatGPT sign-in yields a token bundle in$CODEX_HOME/auth.jsonthat Codex itself refreshes (after ~8 days, or on a 401) and writes back. OpenAI documents copying that file into containers / CI runners as supported, with one hard rule: one copy per serialized stream — never share a copy across concurrent jobs, because a refresh rotates the token and strands the other copies. Our fleet runs Codex turns in many envs concurrently, so the design has to respect that.Docs: https://developers.openai.com/codex/auth (Login on headless devices) and https://developers.openai.com/codex/auth/ci-cd-auth (refresh + persistence rules).
Proposed design — server owns one canonical login and fans it out
data/codex/):codex login --device-authin a throwaway container from the existing workspace image with that dir mounted asCODEX_HOME, streams the URL + one-time code to the UI; operator approves in any browser. Codex 0.154 (current workspace image) supports this. Requires "device code login" enabled once in the operator's ChatGPT security settings.codex loginon a laptop, paste the contents of~/.codex/auth.jsoninto a Settings textarea.auth.jsonintoworkspace/.codex/, setscli_auth_credentials_store = "file"in that env'sconfig.toml, and does not exportCODEX_API_KEY. No container changes, no rebuilds.codex execin the throwaway container against the canonical dir, refreshing the bundle well inside the 8-day window. Envs always receive a fresh copy at turn start, so they never take the refresh path themselves → no concurrent rotation. Safety net: after a turn, copy an env's file back if itslast_refreshis newer than the canonical's.AGENT_USAGE.md/ MCP docs get a note.Verify before building (each a 5-minute check in a workspace container)
CODEX_API_KEYenv var overrideauth.json? Decides whetherin-workspace.shmust unset it in ChatGPT mode.gpt-6-astraand-c model_reasoning_effort=work under subscription auth withcodex exec?Scope / effort
~1 day, all server-side: settings fields + two routes, a small helper for the throwaway container and file sync, the pre-turn copy in the agent spawn path (
src/claude.jscodex agent), Settings UI. Deploys with a server restart, no env rebuilds.Caveats
auth.jsonholds live tokens → same secret handling as the API key (masked, redacted from logs, never in transcripts).🤖 Generated with Claude Code
https://claude.ai/code/session_018puUXTJeSWdbG8ZMCFVqQL