fix(anthropic): never refresh an API-key session into Claude Code OAuth credentials (#75641) - #75697
fix(anthropic): never refresh an API-key session into Claude Code OAuth credentials (#75641)#75697kyssta-exe wants to merge 1 commit into
Conversation
…th credentials (NousResearch#75641) _try_refresh_anthropic_client_credentials() ran before every request (including retries) and called resolve_anthropic_token(), which prefers Claude Code OAuth credentials (~/.claude/.credentials.json, priority 3) over the explicitly selected API key (priority 5). After a transient 429, the retry's request-local client was silently rebuilt with the OAuth token, switching the user's billing identity and producing 'extra usage' 400s. API keys never expire, so only OAuth/setup-token sessions may be refreshed.
|
Thanks for the focused fix. The current-main path confirms the reported credential-identity swap: The change in Automated hermes-sweeper review. |
SummaryTwenty-two PRs address or reference this issue complex, spanning billing-error classification, Anthropic endpoint and credential selection, OAuth tool-name and system-prompt fingerprints, billing attribution, auxiliary refresh, and the API-key-to-OAuth retry identity switch isolated in #75641. The diffs therefore cover several distinct causes that produce the same 400/429 family rather than one interchangeable fix. Related pull requests
Duplicates#6498, #21019, #40020, and #40073 overlap on billing classification and guidance; #17681, #28872, and #46687 cover the MCP-prefix cluster with different strategies; #48177, #48202, and #69844 implement the same billing-marker mechanism; and #76669 is the direct predecessor salvaged into #76807. Suggested consolidationKeep #75697 open with a salvage path: retain its narrow _is_oauth_token guard and regression proving that retries preserve the explicitly selected Console API key, since it is the best existing fix for #75641 and agrees with the visible keep_open review. Treat #76807 as the separate best fix for #65365 pending named tool_choice verification, keep #69844, #72173, and #72263 constrained by their stated review conditions, and do not reopen the policy-blocked #10576, #48177, or #48202. Complex graphflowchart LR
classDef open fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
classDef merged fill:#dcfce7,stroke:#15803d,color:#14532d
classDef closed fill:#e5e7eb,stroke:#6b7280,color:#1f2937
classDef unverified fill:#f3f4f6,stroke:#9ca3af,color:#374151
classDef best stroke-width:3px,stroke:#b45309
classDef target stroke-width:3px,stroke:#4338ca
I75641(["issue #75641 (open)"])
P75697["PR #75697 (open)"]
P75697 -->|best fix| I75641
class I75641 open
class P75697 open
class P75697 best
class P75697 target
click I75641 "https://github.com/NousResearch/hermes-agent/issues/75641"
click P75697 "https://github.com/NousResearch/hermes-agent/pull/75697"
Graph: solid arrow = fixes / best fix, dashed arrow = partial or unverified (see edge label); boxed group = PRs duplicating each other; amber border = best fix; indigo border = target; gray node = closed (state tag in the node label). Cross-PR triage: Reviewed 22 pull requests and 22 issues in this complex. Each diff was read against this issue; Assessment working set: 244 kB of PR diffs, 253 kB of issue/PR text, 82 kB of discussion (169 comments), 110 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch. |
Summary
Fixes #75641: Anthropic API-key requests fail with a 429 and then a confusing OAuth "extra usage" 400 on retry.
Root cause
AIAgent._try_refresh_anthropic_client_credentials()runs before every request-client build — including the retry after a transient 429 — and callsresolve_anthropic_token(). That resolver prioritizes:ANTHROPIC_TOKENenvCLAUDE_CODE_OAUTH_TOKENenv~/.claude.json/~/.claude/.credentials.json) ← wins when presentANTHROPIC_API_KEYenvWhen the user explicitly selected a Console API key (the only credential in
hermes auth list), but Claude Code's credential files still exist on disk (as in the report: "Claude Code's own credential files were not deleted"), the refresh swapped the API key for the Claude Code OAuth token, rebuilt the client, and the retry went out under OAuth billing — producing the400 "Third-party apps now draw from your extra usage"error.Fix
A plain Console API key (
sk-ant-api…) never expires, so there is nothing to refresh for an API-key session._try_refresh_anthropic_client_credentials()now returnsFalseimmediately unless the current credential is OAuth/setup-token shaped (_is_oauth_token). OAuth/setup-token sessions keep the existing refresh behavior (including the token-type-change flag updates).Tests
tests/run_agent/test_run_agent.py::TestOAuthFlagAfterCredentialRefresh— rewrotetest_oauth_flag_updates_api_key_to_oauthto assert the corrected behavior (API-key session is preserved; no OAuth swap, flag staysFalse).test_run_agent.pyrefresh/oauth selection +test_anthropic_third_party_oauth_guard.py+test_anthropic_response_header_capture.py: 227 passed.