Skip to content

fix(auth): preserve independent Codex pool entries on re-auth (#39236) - #39243

Closed
temalo wants to merge 1 commit into
NousResearch:mainfrom
temalo:fix/39236-codex-pool-overwrite
Closed

fix(auth): preserve independent Codex pool entries on re-auth (#39236)#39243
temalo wants to merge 1 commit into
NousResearch:mainfrom
temalo:fix/39236-codex-pool-overwrite

Conversation

@temalo

@temalo temalo commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Fixes #39236.

Why

hermes auth add openai-codex for a second/third OpenAI account silently overwrites earlier independent accounts in the credential pool. The visible labels stay, but the token material behind each label gets replaced with whatever was just authenticated — so hermes auth list openai-codex lies, quota checks lie, and rotation lies.

The root cause is a too-broad sync introduced by #33538 to fix #33000 / #33538. That fix treated every credential_pool entry whose source string is manual:device_code as a legacy alias of the singleton-seeded device_code entry and refreshed them all on every Codex OAuth save. This was correct when manual:device_code only meant "legacy workaround entry" — but the same source string is now also produced by hermes auth add openai-codex for independent ChatGPT accounts. Refreshing every match conflates the two cases and clobbers distinct accounts.

Repro confirmed on upstream/main (a3fb48b) with the issue's synthetic _save_codex_tokens script — two manual:device_code entries (acctA, acctB) end up sharing the latest-authenticated token pair after a third _save_codex_tokens call. After the fix in this PR the same script preserves both entries' original tokens.

What

  • hermes_cli/auth.py_save_codex_tokens now captures the previous singleton tokens before overwriting them, and passes them into _sync_codex_pool_entries as a new keyword arg.
  • hermes_cli/auth.py_sync_codex_pool_entries now treats a manual:device_code entry as a singleton-alias (and refreshes it) only when its current access_token matches the previous singleton access_token. Entries with distinct token material are left alone.
  • hermes_cli/auth.py — error-marker clearing (last_status, last_error_*) is now scoped to entries that were actually refreshed by the current re-auth. An independent account's own 429 quota state survives a re-auth that targeted a different account.
  • tests/hermes_cli/test_auth_codex_provider.py — 5 new tests covering the regression and edge cases, plus the existing test_save_codex_tokens_syncs_manual_device_code_entries rewritten to cover BOTH the legacy-alias path and the independent-account path in one fixture.

The source-string contract (device_code for singleton, manual:device_code for manually-added device-code OAuth) is unchanged. No schema migration. No call-site changes outside _save_codex_tokens. The fix lives entirely inside the helper.

Behaviour change

Zero for users with a single Codex account.

Zero for users whose only manual:device_code pool entry is a legacy alias of the singleton (the #33538 use case) — that entry's tokens still matched the previous singleton at the moment of re-auth, so it still gets refreshed.

Users with multiple independent Codex accounts added via hermes auth add openai-codex now keep their distinct token material across re-auths. Previously each re-auth silently overwrote them.

Test coverage (added)

Test What it asserts
test_save_codex_tokens_does_not_overwrite_independent_manual_entries Re-auth of acctA does not overwrite acctB or acctC pool entries when their tokens never matched the singleton. The headline #39236 regression.
test_save_codex_tokens_still_refreshes_legacy_manual_alias A manual:device_code entry whose tokens DO match the previous singleton is still refreshed on re-auth (preserves the #33538 fix and clears stale error markers).
test_save_codex_tokens_handles_missing_previous_singleton_tokens First-ever Codex save against an auth.json with no providers.openai-codex.tokens block does not crash and does not falsely alias any pre-existing pool entry.
test_save_codex_tokens_alias_match_uses_access_token_only A legacy entry with access_token but no refresh_token (older schema) is still recognized as an alias and refreshed.
test_save_codex_tokens_clears_error_markers_only_on_refreshed_entries An independent account's 429 / quota_exhausted markers survive a re-auth that targeted a different account; the seeded singleton's 401 markers are cleared as before.
test_save_codex_tokens_syncs_manual_device_code_entries (rewritten) Same fixture now covers a legacy-alias entry (refreshed) and an independent entry (preserved) and a manual:api_key entry (untouched) in one pass.

Local: 29 passed in tests/hermes_cli/test_auth_codex_provider.py. Pristine-vs-branch diff over tests/hermes_cli/ shows zero new failures introduced by this change.

Out of scope

  • auth_add_command for openai-codex still routes through _save_codex_tokens (the singleton save path). The narrowed sync makes that safe — a re-auth that targets account N no longer clobbers accounts M ≠ N — but a cleaner long-term design would have hermes auth add openai-codex add a fresh PooledCredential directly (matching the xai-oauth / google-gemini-cli patterns) instead of routing through the singleton. That refactor changes the singleton-vs-pool contract for Codex and warrants its own discussion, so it's deferred to a follow-up.

  • The CLI message printed after auth add openai-codex still resolves the displayed label from the first device_code pool entry (auth_commands.py:321). With the sync narrowed, the wrong-label-shown path identified in the issue is now harder to hit in practice, but the label-resolution code itself is unchanged — that's a separate small fix.

  • No change to agent/credential_pool.py seeding behavior. The _seed_from_singletons path that creates the initial device_code mirror entry is untouched.

  • No change to the manual:api_key handling — those entries were already excluded from the broad sync and remain so.

…search#39236)

The NousResearch#33538 fix refreshed every credential_pool entry with source
"manual:device_code" on every Codex OAuth re-auth, on the assumption that
such entries were always legacy aliases of the singleton from the NousResearch#33000
workaround era. That assumption is no longer true: `hermes auth add
openai-codex` also produces "manual:device_code" entries for independent
ChatGPT accounts, and the broad sync silently clobbered them with the
latest-authenticated token pair (labels preserved, token material
overwritten, status / quota readings then lie).

Narrow the sync: refresh a "manual:device_code" entry only when its
existing access_token matches the previous singleton access_token (true
legacy alias). Entries with distinct token material represent independent
accounts and are now left alone. Error markers are cleared only on
entries actually rewritten, so an independent account's own 429 / 401
state survives a re-auth that targeted a different account.

Tests:
* New: independent acctB/acctC are not overwritten when acctA re-auths.
* New: legacy singleton-alias still refreshed (preserves NousResearch#33538).
* New: missing previous singleton state handled (no crash, no false
  alias match).
* New: access_token-only alias match (legacy schema without
  refresh_token still recognized).
* New: error markers cleared only on entries actually refreshed.
* Updated: existing manual-device-code sync test now covers both the
  legacy-alias path AND the independent-account path in one fixture.

Behaviour change is zero for users with a single Codex account and zero
for users whose only "manual:device_code" entry is the legacy alias of
the singleton. Users with multiple independent Codex accounts added via
`hermes auth add` now keep their distinct token material across
re-auths.

Local: 29 passed in tests/hermes_cli/test_auth_codex_provider.py, no
new failures in tests/hermes_cli/ vs upstream/main baseline.

Fixes NousResearch#39236.
@alt-glitch alt-glitch added type/bug Something isn't working area/auth Authentication, OAuth, credential pools provider/openai OpenAI / Codex Responses API comp/cli CLI entry point, hermes_cli/, setup wizard P3 Low — cosmetic, nice to have labels Jun 4, 2026
@pinguarmy

Copy link
Copy Markdown

Thanks — I tested this locally and I think it correctly addresses the re-auth half of #39236.

One extra detail from my repro: the bug is not only in _save_codex_tokens / re-auth. The hermes auth add openai-codex --type oauth path also needs to preserve independent Codex accounts instead of routing everything back through the singleton token slot.

What I saw on current main:

  • add account A
  • add account B
  • expected: two independent credential_pool entries
  • actual: the second add path still collapses back onto the singleton-style token flow, so the earlier independent account can get clobbered / aliased and labels become misleading

The combined fix that worked for me locally was:

  1. auth-add path: create a fresh credential_pool entry with source=manual:device_code for each newly added Codex OAuth account, instead of rewriting providers.openai-codex.tokens
  2. re-auth path: keep your current preservation logic, but only resync legacy aliases of the previous singleton token

With that combined approach, both of these cases pass:

  • adding multiple independent Codex accounts
  • re-authing the singleton/device_code account without overwriting the independent ones

I also added focused regression tests for both paths locally:

  • test_auth_add_codex_oauth_keeps_distinct_pool_accounts
  • expanded _save_codex_tokens sync coverage for legacy-alias vs independent-account behavior

So from my side: this PR looks like the right fix for the re-auth half, but I think #39236 is only fully closed once the auth-add path is covered too.

teknium1 added a commit that referenced this pull request Jun 8, 2026
hermes auth add openai-codex now creates an independent
manual:device_code pool entry per account instead of routing through
the singleton _save_codex_tokens save path, which collapsed every
added account into the latest login (the second add overwrote the
first account's singleton-mirrored device_code entry). This is the
add-path half of #39236; PR #39243 (already on this branch) fixes the
re-auth half.

manual:device_code entries refresh from their own token pair
(_sync_codex_entry_from_auth_store only adopts the singleton for
source=="device_code"), so they need no providers.openai-codex
shadow. Adding the first credential marks openai-codex active (the
singleton path did this implicitly) so the setup wizard's
get_active_provider() check still passes; subsequent adds leave the
active provider untouched.

Adds SOURCE_MANUAL_DEVICE_CODE constant and a regression test that two
distinct accounts keep distinct token pairs. Updates two existing add
tests to the pool-only behavior.

Co-authored-by: glesperance <info@glesperance.com>
teknium1 added a commit that referenced this pull request Jun 8, 2026
hermes auth add openai-codex now creates an independent
manual:device_code pool entry per account instead of routing through
the singleton _save_codex_tokens save path, which collapsed every
added account into the latest login (the second add overwrote the
first account's singleton-mirrored device_code entry). This is the
add-path half of #39236; PR #39243 (already on this branch) fixes the
re-auth half.

manual:device_code entries refresh from their own token pair
(_sync_codex_entry_from_auth_store only adopts the singleton for
source=="device_code"), so they need no providers.openai-codex
shadow. Adding the first credential marks openai-codex active (the
singleton path did this implicitly) so the setup wizard's
get_active_provider() check still passes; subsequent adds leave the
active provider untouched.

Adds SOURCE_MANUAL_DEVICE_CODE constant and a regression test that two
distinct accounts keep distinct token pairs. Updates two existing add
tests to the pool-only behavior.

Co-authored-by: glesperance <info@glesperance.com>
@teknium1

teknium1 commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Merged via #42316 (rebase, your commit landed on main as 761b744 with your authorship preserved).

Your narrowing of _sync_codex_pool_entries() — refreshing a manual:device_code entry only when its token matches the previous singleton — is the re-auth half of the fix and went in as-is. On top of it we added the add-path half: hermes auth add openai-codex now creates a distinct pool entry per account directly instead of routing through the singleton save (which was collapsing the second account on add, before any re-auth). Both halves verified together via E2E. Thanks for the thorough writeup and tests.

@teknium1 teknium1 closed this Jun 8, 2026
a249169329-cpu pushed a commit to a249169329-cpu/hermes-agent that referenced this pull request Jun 9, 2026
hermes auth add openai-codex now creates an independent
manual:device_code pool entry per account instead of routing through
the singleton _save_codex_tokens save path, which collapsed every
added account into the latest login (the second add overwrote the
first account's singleton-mirrored device_code entry). This is the
add-path half of NousResearch#39236; PR NousResearch#39243 (already on this branch) fixes the
re-auth half.

manual:device_code entries refresh from their own token pair
(_sync_codex_entry_from_auth_store only adopts the singleton for
source=="device_code"), so they need no providers.openai-codex
shadow. Adding the first credential marks openai-codex active (the
singleton path did this implicitly) so the setup wizard's
get_active_provider() check still passes; subsequent adds leave the
active provider untouched.

Adds SOURCE_MANUAL_DEVICE_CODE constant and a regression test that two
distinct accounts keep distinct token pairs. Updates two existing add
tests to the pool-only behavior.

Co-authored-by: glesperance <info@glesperance.com>
changman pushed a commit to changman/hermes-agent that referenced this pull request Jun 10, 2026
hermes auth add openai-codex now creates an independent
manual:device_code pool entry per account instead of routing through
the singleton _save_codex_tokens save path, which collapsed every
added account into the latest login (the second add overwrote the
first account's singleton-mirrored device_code entry). This is the
add-path half of NousResearch#39236; PR NousResearch#39243 (already on this branch) fixes the
re-auth half.

manual:device_code entries refresh from their own token pair
(_sync_codex_entry_from_auth_store only adopts the singleton for
source=="device_code"), so they need no providers.openai-codex
shadow. Adding the first credential marks openai-codex active (the
singleton path did this implicitly) so the setup wizard's
get_active_provider() check still passes; subsequent adds leave the
active provider untouched.

Adds SOURCE_MANUAL_DEVICE_CODE constant and a regression test that two
distinct accounts keep distinct token pairs. Updates two existing add
tests to the pool-only behavior.

Co-authored-by: glesperance <info@glesperance.com>
alt-glitch pushed a commit that referenced this pull request Jun 14, 2026
hermes auth add openai-codex now creates an independent
manual:device_code pool entry per account instead of routing through
the singleton _save_codex_tokens save path, which collapsed every
added account into the latest login (the second add overwrote the
first account's singleton-mirrored device_code entry). This is the
add-path half of #39236; PR #39243 (already on this branch) fixes the
re-auth half.

manual:device_code entries refresh from their own token pair
(_sync_codex_entry_from_auth_store only adopts the singleton for
source=="device_code"), so they need no providers.openai-codex
shadow. Adding the first credential marks openai-codex active (the
singleton path did this implicitly) so the setup wizard's
get_active_provider() check still passes; subsequent adds leave the
active provider untouched.

Adds SOURCE_MANUAL_DEVICE_CODE constant and a regression test that two
distinct accounts keep distinct token pairs. Updates two existing add
tests to the pool-only behavior.

Co-authored-by: glesperance <info@glesperance.com>
davidgut1982 pushed a commit to davidgut1982/hermes-agent that referenced this pull request Jun 17, 2026
hermes auth add openai-codex now creates an independent
manual:device_code pool entry per account instead of routing through
the singleton _save_codex_tokens save path, which collapsed every
added account into the latest login (the second add overwrote the
first account's singleton-mirrored device_code entry). This is the
add-path half of NousResearch#39236; PR NousResearch#39243 (already on this branch) fixes the
re-auth half.

manual:device_code entries refresh from their own token pair
(_sync_codex_entry_from_auth_store only adopts the singleton for
source=="device_code"), so they need no providers.openai-codex
shadow. Adding the first credential marks openai-codex active (the
singleton path did this implicitly) so the setup wizard's
get_active_provider() check still passes; subsequent adds leave the
active provider untouched.

Adds SOURCE_MANUAL_DEVICE_CODE constant and a regression test that two
distinct accounts keep distinct token pairs. Updates two existing add
tests to the pool-only behavior.

Co-authored-by: glesperance <info@glesperance.com>
T02200059 pushed a commit to T02200059/hermes-agent that referenced this pull request Jun 18, 2026
hermes auth add openai-codex now creates an independent
manual:device_code pool entry per account instead of routing through
the singleton _save_codex_tokens save path, which collapsed every
added account into the latest login (the second add overwrote the
first account's singleton-mirrored device_code entry). This is the
add-path half of NousResearch#39236; PR NousResearch#39243 (already on this branch) fixes the
re-auth half.

manual:device_code entries refresh from their own token pair
(_sync_codex_entry_from_auth_store only adopts the singleton for
source=="device_code"), so they need no providers.openai-codex
shadow. Adding the first credential marks openai-codex active (the
singleton path did this implicitly) so the setup wizard's
get_active_provider() check still passes; subsequent adds leave the
active provider untouched.

Adds SOURCE_MANUAL_DEVICE_CODE constant and a regression test that two
distinct accounts keep distinct token pairs. Updates two existing add
tests to the pool-only behavior.

Co-authored-by: glesperance <info@glesperance.com>
xyshanren pushed a commit to xyshanren/hermes-agent-cn that referenced this pull request Jun 25, 2026
hermes auth add openai-codex now creates an independent
manual:device_code pool entry per account instead of routing through
the singleton _save_codex_tokens save path, which collapsed every
added account into the latest login (the second add overwrote the
first account's singleton-mirrored device_code entry). This is the
add-path half of NousResearch#39236; PR NousResearch#39243 (already on this branch) fixes the
re-auth half.

manual:device_code entries refresh from their own token pair
(_sync_codex_entry_from_auth_store only adopts the singleton for
source=="device_code"), so they need no providers.openai-codex
shadow. Adding the first credential marks openai-codex active (the
singleton path did this implicitly) so the setup wizard's
get_active_provider() check still passes; subsequent adds leave the
active provider untouched.

Adds SOURCE_MANUAL_DEVICE_CODE constant and a regression test that two
distinct accounts keep distinct token pairs. Updates two existing add
tests to the pool-only behavior.

Co-authored-by: glesperance <info@glesperance.com>
waefrebeorn pushed a commit to waefrebeorn/slermes that referenced this pull request Jul 2, 2026
hermes auth add openai-codex now creates an independent
manual:device_code pool entry per account instead of routing through
the singleton _save_codex_tokens save path, which collapsed every
added account into the latest login (the second add overwrote the
first account's singleton-mirrored device_code entry). This is the
add-path half of NousResearch#39236; PR NousResearch#39243 (already on this branch) fixes the
re-auth half.

manual:device_code entries refresh from their own token pair
(_sync_codex_entry_from_auth_store only adopts the singleton for
source=="device_code"), so they need no providers.openai-codex
shadow. Adding the first credential marks openai-codex active (the
singleton path did this implicitly) so the setup wizard's
get_active_provider() check still passes; subsequent adds leave the
active provider untouched.

Adds SOURCE_MANUAL_DEVICE_CODE constant and a regression test that two
distinct accounts keep distinct token pairs. Updates two existing add
tests to the pool-only behavior.

Co-authored-by: glesperance <info@glesperance.com>
santhreal pushed a commit to santhreal/hermes-agent that referenced this pull request Jul 13, 2026
hermes auth add openai-codex now creates an independent
manual:device_code pool entry per account instead of routing through
the singleton _save_codex_tokens save path, which collapsed every
added account into the latest login (the second add overwrote the
first account's singleton-mirrored device_code entry). This is the
add-path half of NousResearch#39236; PR NousResearch#39243 (already on this branch) fixes the
re-auth half.

manual:device_code entries refresh from their own token pair
(_sync_codex_entry_from_auth_store only adopts the singleton for
source=="device_code"), so they need no providers.openai-codex
shadow. Adding the first credential marks openai-codex active (the
singleton path did this implicitly) so the setup wizard's
get_active_provider() check still passes; subsequent adds leave the
active provider untouched.

Adds SOURCE_MANUAL_DEVICE_CODE constant and a regression test that two
distinct accounts keep distinct token pairs. Updates two existing add
tests to the pool-only behavior.

Co-authored-by: glesperance <info@glesperance.com>
donbowman pushed a commit to donbowman/hermes-agent that referenced this pull request Jul 13, 2026
hermes auth add openai-codex now creates an independent
manual:device_code pool entry per account instead of routing through
the singleton _save_codex_tokens save path, which collapsed every
added account into the latest login (the second add overwrote the
first account's singleton-mirrored device_code entry). This is the
add-path half of NousResearch#39236; PR NousResearch#39243 (already on this branch) fixes the
re-auth half.

manual:device_code entries refresh from their own token pair
(_sync_codex_entry_from_auth_store only adopts the singleton for
source=="device_code"), so they need no providers.openai-codex
shadow. Adding the first credential marks openai-codex active (the
singleton path did this implicitly) so the setup wizard's
get_active_provider() check still passes; subsequent adds leave the
active provider untouched.

Adds SOURCE_MANUAL_DEVICE_CODE constant and a regression test that two
distinct accounts keep distinct token pairs. Updates two existing add
tests to the pool-only behavior.

Co-authored-by: glesperance <info@glesperance.com>
Gravezzz pushed a commit to Gravezzz/hermes-agent that referenced this pull request Jul 21, 2026
hermes auth add openai-codex now creates an independent
manual:device_code pool entry per account instead of routing through
the singleton _save_codex_tokens save path, which collapsed every
added account into the latest login (the second add overwrote the
first account's singleton-mirrored device_code entry). This is the
add-path half of NousResearch#39236; PR NousResearch#39243 (already on this branch) fixes the
re-auth half.

manual:device_code entries refresh from their own token pair
(_sync_codex_entry_from_auth_store only adopts the singleton for
source=="device_code"), so they need no providers.openai-codex
shadow. Adding the first credential marks openai-codex active (the
singleton path did this implicitly) so the setup wizard's
get_active_provider() check still passes; subsequent adds leave the
active provider untouched.

Adds SOURCE_MANUAL_DEVICE_CODE constant and a regression test that two
distinct accounts keep distinct token pairs. Updates two existing add
tests to the pool-only behavior.

Co-authored-by: glesperance <info@glesperance.com>
leewenjie pushed a commit to leewenjie/hermes-agent that referenced this pull request Aug 7, 2026
hermes auth add openai-codex now creates an independent
manual:device_code pool entry per account instead of routing through
the singleton _save_codex_tokens save path, which collapsed every
added account into the latest login (the second add overwrote the
first account's singleton-mirrored device_code entry). This is the
add-path half of NousResearch#39236; PR NousResearch#39243 (already on this branch) fixes the
re-auth half.

manual:device_code entries refresh from their own token pair
(_sync_codex_entry_from_auth_store only adopts the singleton for
source=="device_code"), so they need no providers.openai-codex
shadow. Adding the first credential marks openai-codex active (the
singleton path did this implicitly) so the setup wizard's
get_active_provider() check still passes; subsequent adds leave the
active provider untouched.

Adds SOURCE_MANUAL_DEVICE_CODE constant and a regression test that two
distinct accounts keep distinct token pairs. Updates two existing add
tests to the pool-only behavior.

Co-authored-by: glesperance <info@glesperance.com>
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 comp/cli CLI entry point, hermes_cli/, setup wizard P3 Low — cosmetic, nice to have provider/openai OpenAI / Codex Responses API type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: openai-codex OAuth add/reauth overwrites existing credential-pool entries while preserving stale labels re-auth does not sync credential_pool

4 participants