Skip to content

fix(anthropic): never refresh an API-key session into Claude Code OAuth credentials (#75641) - #75697

Open
kyssta-exe wants to merge 1 commit into
NousResearch:mainfrom
kyssta-exe:auto-fix/issue-75641
Open

fix(anthropic): never refresh an API-key session into Claude Code OAuth credentials (#75641)#75697
kyssta-exe wants to merge 1 commit into
NousResearch:mainfrom
kyssta-exe:auto-fix/issue-75641

Conversation

@kyssta-exe

Copy link
Copy Markdown
Contributor

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 calls resolve_anthropic_token(). That resolver prioritizes:

  1. ANTHROPIC_TOKEN env
  2. CLAUDE_CODE_OAUTH_TOKEN env
  3. Claude Code credentials (~/.claude.json / ~/.claude/.credentials.json) ← wins when present
  4. credential-pool OAuth entry
  5. ANTHROPIC_API_KEY env

When 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 the 400 "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 returns False immediately 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 — rewrote test_oauth_flag_updates_api_key_to_oauth to assert the corrected behavior (API-key session is preserved; no OAuth swap, flag stays False).
  • test_run_agent.py refresh/oauth selection + test_anthropic_third_party_oauth_guard.py + test_anthropic_response_header_capture.py: 227 passed.

…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.
@teknium1

Copy link
Copy Markdown
Contributor

Thanks for the focused fix. The current-main path confirms the reported credential-identity swap: run_agent.py:4883 invokes the refresh before each request-local Anthropic client, and run_agent.py:5194 unconditionally resolves a replacement token. The resolver gives Claude Code credential files precedence over ANTHROPIC_API_KEY in agent/anthropic_adapter.py:1345-1390.

The change in b64ca2f0ef38 uses the existing classifier, whose sk-ant-api exclusion and OAuth token recognition are defined in agent/anthropic_adapter.py:395-420. It therefore preserves OAuth refresh behavior while preventing Console API-key sessions from silently switching billing identity. The updated regression test complements the existing OAuth refresh coverage at tests/run_agent/test_run_agent.py:4884-4919.

Automated hermes-sweeper review.

@alt-glitch alt-glitch added type/bug Something isn't working comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint provider/anthropic Anthropic native Messages API area/auth Authentication, OAuth, credential pools P2 Medium — degraded but workaround exists sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data labels Jul 31, 2026
@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform area/sessions Session lifecycle, resume, persistence, history labels Jul 31, 2026
@GottZ

GottZ commented Aug 3, 2026

Copy link
Copy Markdown

This was generated by AI during triage.

Summary

Twenty-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 consolidation

Keep #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 graph

flowchart 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"
Loading

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.

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 area/sessions Session lifecycle, resume, persistence, history comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P2 Medium — degraded but workaround exists provider/anthropic Anthropic native Messages API sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Anthropic API-key request returns 429, then retry produces OAuth extra-usage 400

4 participants