Skip to content

fix(codex): bind-mount host auth.json so refreshed tokens reach future spawns#2534

Open
IamAdamJowett wants to merge 1 commit into
nanocoai:providersfrom
IamAdamJowett:fix/codex-auth-bind-mount
Open

fix(codex): bind-mount host auth.json so refreshed tokens reach future spawns#2534
IamAdamJowett wants to merge 1 commit into
nanocoai:providersfrom
IamAdamJowett:fix/codex-auth-bind-mount

Conversation

@IamAdamJowett
Copy link
Copy Markdown
Contributor

What

Changes src/providers/codex.ts to bind-mount the host's ~/.codex/auth.json directly into each container instead of copying it at spawn time. The per-session codex dir is still mounted (for config.toml); the auth.json file mount sits on top of it.

Why

ChatGPT OAuth uses single-use rotating refresh tokens. With the previous copy-based approach, once any codex session refreshed its token, the host's auth.json was left untouched — so every subsequent codex spawn copied a dead token and failed with refresh_token_reused. The failure is silent at the user level: messages_in get marked completed, no outbound row is written, and the operator just sees "the agent went quiet."

Hit in practice this week — a fresh codex agent spawned ~2.5h after the first one of the day stayed silent through three consecutive operator check-ins. Container logs showed the auth manager retrying refresh_token_reused on every poll.

How it works

- fs.copyFileSync(hostAuth, path.join(codexDir, 'auth.json'));
+ mounts.push({
+   hostPath: hostAuth,
+   containerPath: '/home/node/.codex/auth.json',
+   readonly: false,
+ });

The container sees the same inode as the host. When codex refreshes its access token in-place, the new tokens are written back through the bind mount to the host file, and the next codex spawn picks them up.

The existing per-session codex dir mount is preserved so config.toml rewrites stay session-local (the container rewrites this on every wake with container-appropriate MCP server paths). The file mount overlays cleanly on top.

If the user hasn't run codex login yet (no host auth.json), the file mount is skipped and codex falls back to OPENAI_API_KEY — same behaviour as before for that case.

How it was tested

  • Triggered a fresh codex spawn after the patch
  • Verified /proc/mounts inside the container shows both the dir mount and the file mount for auth.json
  • Confirmed host and container see the same inode (stat -c '%i' /home/adam/.codex/auth.json vs docker exec … stat -c '%i' /home/node/.codex/auth.json)
  • Container produced a normal agent reply with zero refresh_token_reused / token_expired entries in logs

Caveat I haven't been able to verify in a short test: a real token refresh propagating back to the host file requires waiting for the access token to expire (~1h). If codex ever switches to atomic-rename writes for auth.json, the file bind mount would silently regress to leaving the host file stale. Worth keeping an eye on if refresh_token_reused re-appears.

Usage

No user-facing changes. Applies automatically to any agent group using provider: codex.

…e spawns

Codex previously copied ~/.codex/auth.json into each session at spawn
time. ChatGPT OAuth uses single-use rotating refresh tokens — once one
session refreshed, the host file went stale and every subsequent codex
spawn copied a dead token, failing silently with refresh_token_reused
(messages_in marked completed, no outbound row). Bind-mount the file
instead so in-place refreshes propagate back to the host.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant