Skip to content

fix(anthropic): OAuth token endpoint UA must not be claude-code/ (login 429, #48534) - #58178

Merged
kshitijk4poor merged 2 commits into
NousResearch:mainfrom
kshitijk4poor:salvage/57922-oauth-ua
Jul 4, 2026
Merged

fix(anthropic): OAuth token endpoint UA must not be claude-code/ (login 429, #48534)#58178
kshitijk4poor merged 2 commits into
NousResearch:mainfrom
kshitijk4poor:salvage/57922-oauth-ua

Conversation

@kshitijk4poor

Copy link
Copy Markdown
Collaborator

Summary

hermes auth add anthropic (Claude Pro/Max OAuth login) is 100% broken on current main — token exchange fails with HTTP 429. This fixes it by sending a non-claude-code/ User-Agent on the OAuth token endpoint only.

Root cause: Anthropic rate-limits (429) any /v1/oauth/token request whose UA prefix is claude-code/ (the anti-abuse net for Max-sub-as-API-key). This is the same prefix-block that #48534 first hit on claude-cli/ (404), which #56263 dodged by switching to claude-code/ — it held ~2 weeks and is now blocked too. Version bumps can't help; the gate is prefix-based.

Salvaged from @mssteuer's #57922, keeping only the load-bearing UA fix. The bundled authorize-host (claude.aiclaude.com/cai) and scope-expansion changes were dropped — verified they aren't required (the claude.com/cai authorize URL just 307-redirects back to claude.ai, and the UA fix alone clears the 429).

Changes

  • agent/anthropic_adapter.py: new _OAUTH_TOKEN_USER_AGENT (axios/) applied at the two refresh POSTs and the login-exchange POST. The inference client (build_anthropic_kwargs, /v1/messages) is untouched — it keeps claude-code/ + x-app: cli, which is required there and not throttled. Two endpoints, opposite UA requirements.
  • tests/agent/test_anthropic_oauth_ua_prefix.py: split invariant — token endpoint must NOT use a claude-code//claude-cli/ UA; inference still must.
  • tests/agent/test_anthropic_adapter.py: isolate two _refresh_oauth_token tests from live ~/.claude creds.

Validation

Live probe against platform.claude.com/v1/oauth/token (throwaway code — a 429 is applied before code validation, so 400 invalid_grant = request accepted, only the fake code rejected):

User-Agent Result
claude-code/2.1.200 (external, cli) (current main) 429 rate_limit
Mozilla/5.0 429 ❌
axios/1.7.9 (this PR) 400 invalid_grant ✅ reached validation

E2E: driving the real run_hermes_oauth_login_pure code path with the actual _OAUTH_TOKEN_USER_AGENT constant returns 400 (past the 429 wall) vs 429 with the old UA.

tests/agent/test_anthropic_oauth_ua_prefix.py — 4 passed. (The 17 test_anthropic_adapter.py failures are pre-existing on clean main, from local ~/.claude creds leaking on dev machines; they pass in CI.)

Closes #48534. Credit: @mssteuer (#57922).

mssteuer and others added 2 commits July 4, 2026 15:03
…in 429, NousResearch#48534)

hermes auth add anthropic fails 100% at token exchange with HTTP 429 while
Claude Code /login succeeds through the same client_id/redirect/scope. The
discriminator is the User-Agent on the /v1/oauth/token request.

Verified live against platform.claude.com (throwaway code, nothing burned):
  claude-code/2.1.200 (external, cli)  -> 429 rate_limit   (Hermes, blocked)
  Mozilla/5.0                          -> 429 rate_limit
  axios/1.7.9                          -> 400 invalid_grant (reached validation)
  node / empty / SDK-style UAs         -> 400 invalid_grant

Anthropic now rate-limits token-endpoint requests whose UA starts with
claude-code/ (the anti-abuse net for Max-sub-as-API-key). This is the same
prefix-block shape that NousResearch#48534 first hit on claude-cli/, then NousResearch#56263 dodged by
switching to claude-code/ — which held ~2 weeks and is now blocked too. Bumping
_CLAUDE_CODE_VERSION_FALLBACK cannot help; the gate is prefix-based.

Fix: shared _OAUTH_TOKEN_USER_AGENT (axios/) on the token endpoint only — the
two refresh POSTs (refresh_anthropic_oauth_pure) and the login exchange POST
(run_hermes_oauth_login_pure). The real Claude Code CLI exchanges the auth code
with a bare axios client, NOT its claude-code/ inference UA.

The INFERENCE client (build_anthropic_kwargs, /v1/messages) is deliberately left
on claude-code/ + x-app: cli — that fingerprint is required there and is NOT
throttled on the messages API. Two endpoints, opposite UA requirements.

Also isolate two _refresh_oauth_token tests from live ~/.claude creds and update
the UA regression tests to assert the split (token endpoint uses a
non-claude-code UA while inference keeps claude-code/).

Verified E2E: Hermes' own login path now returns 400 (past the 429 wall)
instead of 429, using the real _OAUTH_TOKEN_USER_AGENT constant against the live
platform.claude.com token endpoint.

Salvaged from NousResearch#57922 (authorize-host + scope changes dropped as non-load-bearing;
they only add a redirect hop back to claude.ai and the UA fix alone clears 429).
…in isolation)

Two review findings on the NousResearch#57922 salvage:

1. Stale inline comment at the login-exchange site still claimed the token
   endpoint uses the claude-code/ UA prefix and 404s claude-cli/ — now
   contradicts the axios/ fix. Repointed it at _OAUTH_TOKEN_USER_AGENT.

2. The inherited Path.home test isolation on the three TestRefreshOauthToken
   tests only stubbed the ~/.claude *file* source, not the macOS Keychain.
   _refresh_oauth_token re-reads read_claude_code_credentials() (keychain
   first) in its adopt-already-refreshed branch, so on any macOS dev/CI runner
   with real Claude Code creds the branch short-circuits and the 3 tests fail.
   Stub read_claude_code_credentials -> None so the tests are hermetic.

(The remaining TestResolveAnthropicToken/TestResolveWithRefresh/TestRunOauthSetupToken
failures on macOS are the same pre-existing keychain-leak class on origin/main,
unrelated to this OAuth-UA fix, and pass in CI — left out of scope.)
@kshitijk4poor
kshitijk4poor enabled auto-merge (rebase) July 4, 2026 09:40
@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 P1 High — major feature broken, no workaround labels Jul 4, 2026
@kshitijk4poor
kshitijk4poor merged commit 09693cd into NousResearch:main Jul 4, 2026
29 checks passed
@kshitijk4poor
kshitijk4poor deleted the salvage/57922-oauth-ua branch August 5, 2026 07:10
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 comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P1 High — major feature broken, no workaround provider/anthropic Anthropic native Messages API type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Anthropic Max OAuth fails: token exchange 404s because Anthropic now blocks the claude-cli/ User-Agent

3 participants