fix(auth): return exhausted credential key instead of empty string - #40961
Open
liuhao1024 wants to merge 2 commits into
Open
fix(auth): return exhausted credential key instead of empty string#40961liuhao1024 wants to merge 2 commits into
liuhao1024 wants to merge 2 commits into
Conversation
…en all pool entries are in cooldown When all credential pool entries are in exhaustion cooldown (e.g. after 429 quota exhausted), _resolve_api_key_provider_secret() returns an empty api_key via pool.peek() -> None -> "", "". The empty key causes a misleading 401 Unauthorized from the upstream API instead of a clear 429/402 error. Fix: after peek() returns None, fall through to the first raw pool entry and return its key anyway. This ensures the real upstream error surfaces so users see the actual cause (quota exhausted, billing issue) rather than a confusing auth failure.
4 tasks
teknium1
reviewed
Jul 14, 2026
teknium1
left a comment
Contributor
There was a problem hiding this comment.
Thanks for addressing a live resolver failure. Current main still has the reported path: hermes_cli/auth.py:600-609 calls pool.peek() and returns an empty key when no entry is available.
Problems
hermes_cli/auth.py:609iterates all raw pool entries, includingSTATUS_DEADcredentials.agent/credential_pool.py:59-64and1467-1496define those as permanently excluded from use; this fallback would bypass that guarantee and can resend a revoked/invalidated credential.- The PR adds no regression coverage for the
peek() is Nonepath. Existing tests attests/tools/test_credential_pool_env_fallback.py:181-214cover only a normal selected entry and an empty pool.
Suggested changes
- Gate the fallback on
entry is Noneand restrict it to explicitlySTATUS_EXHAUSTEDentries, neverSTATUS_DEAD. - Add tests for all-exhausted returning a key and all-DEAD remaining unresolved.
This is an automated hermes-sweeper review.
| "credential pool: all %s entries exhausted; " | ||
| "using %s anyway for clearer upstream error", | ||
| provider_id, e.label or e.id[:8], | ||
| ) |
Contributor
There was a problem hiding this comment.
This raw loop also considers STATUS_DEAD entries. CredentialPool._available_entries() deliberately excludes DEAD credentials permanently (agent/credential_pool.py:1467-1496), so restrict this fallback to entries explicitly marked exhausted; otherwise a revoked/invalidated credential can be sent again.
This was referenced Aug 3, 2026
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
Fixes #40960
When all credential pool entries are in exhaustion cooldown (e.g. after 429),
_resolve_api_key_provider_secret()returns an empty api_key viapool.peek() → None → ("", ""). The empty key causes a misleading 401 Unauthorized from the upstream API instead of the real 429/402 error.Changes
hermes_cli/auth.py— Afterpool.peek()returnsNone(all entries in cooldown), fall through to iteratepool._entriesand return the first entry's key with alogger.warning. This ensures the upstream API returns the actual error code and message.Before
After
Testing
python -m pytest tests/ -k credential_pool)test_anthropic_adapter.py::TestRunOauthSetupTokenis unrelated (MagicMock type issue)