fix(auth): keep Codex OAuth pool accounts distinct on add + re-auth (#39236) - #42316
Merged
Conversation
The #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 #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 #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 #39236.
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>
Contributor
🔎 Lint report:
|
| Rule | Count |
|---|---|
invalid-argument-type |
1 |
First entries
hermes_cli/auth_commands.py:317: [invalid-argument-type] invalid-argument-type: Argument to function `_save_codex_tokens` is incorrect: Expected `str`, found `Any | None`
Unchanged: 5518 pre-existing issues carried over.
Diagnostics are surfaced as warnings — this check never fails the build.
This was referenced Jun 8, 2026
1 task
12 tasks
Open
19 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
hermes auth add openai-codexnow keeps multiple ChatGPT OAuth accounts distinct in the credential pool — both when adding them and when re-authenticating one. Previously a second account silently collapsed into the latest login, so rotation/failover always used one account.Closes #39236 (and the duplicate add-path issue tracked by #32664).
Root cause — two paths, both collapsing accounts
hermes auth add openai-codexrouted through the singleton_save_codex_tokenssave, which writesproviders.openai-codex.tokensand mirrors it into onedevice_codepool entry. A second add overwrote that singleton mirror instead of creating an independent entry, so two labels shared one token pair._sync_codex_pool_entries()(from the openai-codex OAuth reauthorization can leave stale credential; auth add workaround required #33538 fix) refreshed everymanual:device_codeentry on any re-auth, clobbering independent accounts with the newest tokens.Changes
hermes_cli/auth_commands.py— Codex add path now builds a distinctPooledCredentialper account (source=manual:device_code) and callspool.add_entry(), matching thexai-oauth/google-gemini-cli/qwen-oauthpatterns. No singleton round-trip.hermes_cli/auth.py—_sync_codex_pool_entries()now refreshes amanual:device_codeentry only when itsaccess_tokenmatches the previous singleton (a true legacy alias from the re-auth does not sync credential_pool #33000 workaround era); independent accounts are left alone. Error-marker clearing is scoped to entries actually refreshed. (PR fix(auth): preserve independent Codex pool entries on re-auth (#39236) #39243, @temalo)hermes_cli/auth.py— newmark_provider_active_if_unset(); the first Codex add marksopenai-codexactive (the singleton path did this implicitly) so the setup wizard'sget_active_provider()check still passes. Subsequent adds leave the active provider untouched.agent/credential_pool.py—SOURCE_MANUAL_DEVICE_CODEconstant. (PR fix(auth): keep Codex OAuth pool accounts distinct #32664, @glesperance)manual:device_codepool entries refresh from their own token pair (_sync_codex_entry_from_auth_storeonly adopts the singleton forsource=="device_code"), so they need noproviders.openai-codexshadow.Validation
E2E (isolated
HERMES_HOME, realauth_add_command/_save_codex_tokens):pytest tests/hermes_cli/test_auth_commands.py tests/hermes_cli/test_auth_codex_provider.py→ 79 passed.tests/agent/test_credential_pool.py→ 78 passed.Credit
SOURCE_MANUAL_DEVICE_CODE+ the distinct-accounts add-path direction (fix(auth): keep Codex OAuth pool accounts distinct #32664), co-authored on the add-path commit.Infographic