fix(anthropic): OAuth token endpoint UA must not be claude-code/ (login 429, #48534) - #58178
Merged
kshitijk4poor merged 2 commits intoJul 4, 2026
Merged
Conversation
…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
enabled auto-merge (rebase)
July 4, 2026 09:40
This was referenced Jul 4, 2026
Closed
This was referenced Jul 12, 2026
Closed
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
hermes auth add anthropic(Claude Pro/Max OAuth login) is 100% broken on currentmain— 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/tokenrequest whose UA prefix isclaude-code/(the anti-abuse net for Max-sub-as-API-key). This is the same prefix-block that #48534 first hit onclaude-cli/(404), which #56263 dodged by switching toclaude-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.ai→claude.com/cai) and scope-expansion changes were dropped — verified they aren't required (theclaude.com/caiauthorize URL just 307-redirects back toclaude.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 keepsclaude-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 aclaude-code//claude-cli/UA; inference still must.tests/agent/test_anthropic_adapter.py: isolate two_refresh_oauth_tokentests from live~/.claudecreds.Validation
Live probe against
platform.claude.com/v1/oauth/token(throwaway code — a 429 is applied before code validation, so400 invalid_grant= request accepted, only the fake code rejected):claude-code/2.1.200 (external, cli)(current main)Mozilla/5.0axios/1.7.9(this PR)E2E: driving the real
run_hermes_oauth_login_purecode path with the actual_OAUTH_TOKEN_USER_AGENTconstant returns 400 (past the 429 wall) vs 429 with the old UA.tests/agent/test_anthropic_oauth_ua_prefix.py— 4 passed. (The 17test_anthropic_adapter.pyfailures are pre-existing on cleanmain, from local~/.claudecreds leaking on dev machines; they pass in CI.)Closes #48534. Credit: @mssteuer (#57922).