Skip to content

fix(codex): write rotated OAuth grant through to global root (multi-profile rotation race) - #48416

Closed
spiky02plateau wants to merge 2 commits into
NousResearch:mainfrom
spiky02plateau:fix/codex-oauth-writethrough-global-root
Closed

fix(codex): write rotated OAuth grant through to global root (multi-profile rotation race)#48416
spiky02plateau wants to merge 2 commits into
NousResearch:mainfrom
spiky02plateau:fix/codex-oauth-writethrough-global-root

Conversation

@spiky02plateau

Copy link
Copy Markdown
Contributor

What does this PR do?

Fixes a multi-profile OAuth rotation race for openai-codex — the exact analog of the xAI OAuth race that #43589 documented and that was fixed by the write-through in #46614 (commit 497352bc4).

The race: openai-codex rotates the refresh_token on every refresh. When a profile resolves a Codex grant from the global-root auth.json fallback (it has no own providers.openai-codex block) and the credential pool then rotates that grant, the rotated chain is written only to the profile's store — never back to root. Root is left holding a now-revoked refresh token, and every other profile reading the stale root grant dies with refresh_token_reused once its access token expires.

This PR adds the write-through to global root for openai-codex, mirroring the accepted xAI fix 1:1.

Related Issue

Fixes #48415

Direct analog of #43589 (xAI OAuth), whose write-through fix landed in #46614 / 497352bc4.

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)

Changes Made

  • hermes_cli/auth.py — two new helpers placed adjacent to and mirroring the xAI pair (_profile_has_own_xai_oauth_state / _write_through_xai_oauth_to_global_root):
    • _profile_has_own_codex_oauth_state(auth_store) — distinguishes a profile that genuinely shadows the root grant (has its own providers.openai-codex block) from one that only reads root via fallback.
    • _write_through_codex_oauth_to_global_root(state) — best-effort, TOCTOU-safe write-through that reuses _save_auth_store with an explicit target path. Carries the same pytest seat belt that refuses to write the real ~/.hermes/auth.json under PYTEST_CURRENT_TEST. Classic mode (profile == root) is a no-op.
  • agent/credential_pool.py — in CredentialPool._sync_device_code_entry_to_auth_store, the openai-codex branch now detects the read-from-root case (profile-mode and no own block) and, after the profile save, writes the rotated chain through to root. Strictly best-effort: a failed root write is swallowed and never breaks the profile's own successful save. Profiles that genuinely shadow root are untouched.
  • scripts/release.py — adds the AUTHOR_MAP entry for the contributor email so the attribution check passes (same housekeeping fix(auth): resolve xAI OAuth credentials across profiles + write rotated tokens back to root #46614 did for its salvaged author).

Why it's safe

  • Best-effort, fail-open. A failed root write-through degrades to the pre-existing behavior (root stale) and never breaks the profile's own save — guarded by a swallowed exception at both the helper and call site.
  • Profiles that own their block are untouched. The write-through only fires when the profile lacks its own providers.openai-codex block (i.e. it genuinely read root via fallback). A profile that deliberately shadows root is never clobbered.
  • Classic mode is a no-op. When profile == root (_global_auth_file_path() is None), the profile save already hit root and the write-through returns early.
  • Follows an existing accepted pattern. The helpers, the seat belt, the TOCTOU-safe reuse of _save_auth_store(..., target_path), and the call-site structure are a faithful 1:1 mirror of the xAI fix already merged in fix(auth): resolve xAI OAuth credentials across profiles + write rotated tokens back to root #46614.

How to Test

  1. scripts/run_tests.sh tests/agent/test_codex_oauth_writethrough.py — the new suite (4 tests).
  2. Manual repro of the bug: on a multi-profile install, give two profiles no own providers.openai-codex block (both read root via fallback), let one refresh its Codex grant, then let the other's access token expire — before this fix the second dies with refresh_token_reused; after it, root carries the rotated chain and the second profile keeps working.

Tested on macOS (Python 3.11).

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(codex):, chore:)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix (the fix + its tests + the required AUTHOR_MAP entry)
  • I've run the tests and they pass (tests/agent/test_codex_oauth_writethrough.py 4 passed; tests/agent/test_credential_pool.py + tests/hermes_cli/test_xai_oauth_writethrough.py 82 passed)
  • I've added tests for my changes
  • I've tested on my platform: macOS (Python 3.11)

Documentation & Housekeeping

  • I've updated relevant documentation — N/A (internal best-effort write-through; no user-facing surface or config key)
  • I've updated cli-config.yaml.example if I added/changed config keys — N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — N/A
  • I've considered cross-platform impact — N/A (pure dict/JSON store logic; reuses the existing atomic _save_auth_store writer, no new platform primitives)
  • I've updated tool descriptions/schemas if I changed tool behavior — N/A

Tests

tests/agent/test_codex_oauth_writethrough.py mirrors tests/hermes_cli/test_xai_oauth_writethrough.py and drives the real on-disk save path:

  • write-through fires and updates root when the profile lacks its own providers.openai-codex block;
  • no-op in classic mode (profile == root);
  • a profile that genuinely shadows root is not clobbered;
  • a failed root write is swallowed and the profile's own save still succeeds.

4 passed. tests/agent/test_credential_pool.py + tests/hermes_cli/test_xai_oauth_writethrough.py stay green (82 passed).

🤖 Generated with Claude Code

spiky02plateau and others added 2 commits June 18, 2026 14:23
…rofile rotation race)

When a profile resolves an openai-codex grant from the global-root fallback
(the profile has no own providers.openai-codex block) and the credential pool
rotates that grant, the rotated refresh_token was written only to the
PROFILE auth store. Because openai-codex rotates the refresh_token on every
refresh, root was left holding a now-revoked refresh token — and every other
profile reading the stale root grant later died with refresh_token_reused
once its access token expired.

Hermes already fixes the identical race for xAI OAuth (NousResearch#43589) via a
write-through to the global root. This adds the equivalent for openai-codex:

- _profile_has_own_codex_oauth_state / _write_through_codex_oauth_to_global_root
  in hermes_cli/auth.py, faithfully mirroring the xAI helpers (same best-effort
  error swallowing, same pytest seat belt that refuses to write the real
  ~/.hermes/auth.json under PYTEST_CURRENT_TEST).
- In CredentialPool._sync_device_code_entry_to_auth_store, the openai-codex
  branch now detects the read-from-root case (profile-mode AND no own block)
  and, after the profile save, writes the rotated chain through to root too.
  The write-through is strictly best-effort — a failed root write never breaks
  the profile's own successful save. A profile that genuinely shadows root
  (has its own block) is left untouched; classic mode is a no-op.

Adds tests/agent/test_codex_oauth_writethrough.py mirroring the xAI
write-through test: write-through fires when the profile lacks its own block,
is a no-op in classic mode, does not touch root when the profile shadows it,
and swallows root-write errors without breaking the profile save.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Maps the contributor email to the GitHub username so the
contributor-attribution check (.github/workflows/contributor-check.yml)
passes for this PR's Python changes.
@alt-glitch alt-glitch added type/security Security vulnerability or hardening comp/cli CLI entry point, hermes_cli/, setup wizard comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint area/auth Authentication, OAuth, credential pools area/config Config system, migrations, profiles provider/openai OpenAI / Codex Responses API P1 High — major feature broken, no workaround labels Jun 18, 2026
@egilewski

Copy link
Copy Markdown
Contributor

merge conflicts

This PR does not merge cleanly with the base branch. Please rebase or merge current main and resolve the conflicts if it's still relevant.

Signed: GPT-5.5-medium in Codex

@spiky02plateau

Copy link
Copy Markdown
Contributor Author

Closing as moot. This deployment moved to native per-gateway independent Codex pools — each profile gets its own manual:device_code grants via separate device-code logins, with fill_first for primary->backup failover. That makes refresh-token write-through to global root unnecessary, and counterproductive: it would propagate one grant's rotated refresh token across stores, which is the shared-refresh-token-family stranding we were avoiding. Root cause was grants being copied across auth stores, not a missing write-through. Native multi-account support covers the multi-profile case cleanly, so this patch is retired. Thanks!

@tonydwb tonydwb left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Summary

Verdict: Approved

Bug Fix — Looks Good

  • Root cause addressed: When a profile uses the global-root fallback for openai-codex OAuth (no own providers.openai-codex block) and the grant rotates at runtime, the rotated state was only saved to the profile's auth store, not back to global root. Other profiles reading the stale global root grant would hit refresh_token_reused errors.
  • Correct fix: After saving the profile's auth store, write through the rotated state to global root when the profile was using the fallback (no own codex state)
  • Mirrors xAI fix (#43589): Same write-through pattern applied for xai-oauth is now applied for openai-codex
  • Best-effort write-through: Wrapped in try/except so a failed root write never breaks the profile's successful save
  • New helpers: _write_through_codex_oauth_to_global_root() and _profile_has_own_codex_oauth_state() keep the logic clean
  • No debug artifacts or secrets

Reviewed by Hermes Agent

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/auth Authentication, OAuth, credential pools area/config Config system, migrations, profiles comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/cli CLI entry point, hermes_cli/, setup wizard P1 High — major feature broken, no workaround provider/openai OpenAI / Codex Responses API type/security Security vulnerability or hardening

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: openai-codex multi-profile rotation race — rotated grant not written through to global root (Codex analog of #43589)

4 participants