fix(auth): codex chat path falls back to credential_pool when singleton is empty - #33189
Merged
Conversation
…on is empty Closes #32992. The chat path resolves Codex credentials via `resolve_codex_runtime_credentials` which only reads `providers.openai-codex.tokens` (the singleton). The auxiliary path uses `_read_codex_access_token` which checks the credential_pool first. For users whose tokens live only in the pool — manual seed, partial re-auth, restore from backup, or any state where the singleton is empty but the pool is healthy — the chat path raised AuthError or (worse, since OpenAI(api_key='') silently attaches no header) the wire saw HTTP 401 "Missing Authentication header" while the auxiliary path worked fine. This adds a pool fallback to `resolve_codex_runtime_credentials`: when the singleton has no usable access_token, scan `credential_pool.openai-codex` for the first entry that has a non-empty access_token and isn't in an exhaustion cooldown window (`last_error_reset_at` in the future). If found, return that token with `source="credential_pool"`. If no usable entry exists, the original AuthError propagates as before. Regression tests cover: - Empty singleton + healthy pool entry → pool token returned - Pool fallback skips entries currently in cooldown - Empty singleton + empty/wedged pool → AuthError propagates (existing contract preserved)
Contributor
🔎 Lint report:
|
1 task
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.
Closes #32992.
Summary
The reporter (#32992) hit a bare HTTP 401
Missing Authentication headeronhermes chatwith theopenai-codexprovider, despite a validaccess_tokenbeing present incredential_pool.openai-codex[0]. They explicitly noted: "The auxiliary client appears to correctly attach the header via_pool_runtime_api_key()→OpenAI(api_key=...). The chat-command path uses a different code path that doesn't go through this flow."That's correct — there's a real divergence:
_read_codex_access_tokeninagent/auxiliary_client.py): tries pool first via_select_pool_entry, falls back to singleton.resolve_codex_runtime_credentialsinhermes_cli/auth.py): reads ONLY the singleton (providers.openai-codex.tokens) via_read_codex_tokens. If the singleton is empty, it raisesAuthError(visible) — but if a stale-singleton-but-fresh-pool state somehow returned an empty string instead of raising,OpenAI(api_key="")would silently attach no Authorization header and produce exactly the 401 the reporter saw.This PR closes the divergence: when
_read_codex_tokens()raisesAuthError,resolve_codex_runtime_credentialsnow scanscredential_pool.openai-codexfor the first usable entry (non-emptyaccess_token, not currently in an exhaustion cooldown vialast_error_reset_at). If found, it returns that token withsource="credential_pool". If no usable pool entry exists, the originalAuthErrorpropagates — the existing contract is preserved.Changes
hermes_cli/auth.py:resolve_codex_runtime_credentialswraps_read_codex_tokens()in try/exceptAuthError. On AuthError, calls new_pool_codex_access_token()helper; falls back to the pool token when found, re-raises otherwise._pool_codex_access_token()helper readscredential_pool.openai-codexfromauth.json, skips entries with emptyaccess_tokenor currently in cooldown, returns the first usable token.tests/hermes_cli/test_auth_codex_provider.py: three new regression testsWhy this complements #33164
PR #33164 (merged earlier today, also from @konsisumer) addressed the write side: when re-auth lands fresh tokens in the singleton, mirror them to the pool. This PR addresses the read side: when the chat path queries the singleton and finds nothing, fall back to the pool. Together they keep the two stores in sync regardless of which one received the write.
Validation
tests/hermes_cli/test_auth_codex_provider.py→ 23/23 passing (3 new + 20 existing)resolve_runtime_provider(requested="openai-codex")Infographic