diff --git a/agent/anthropic_adapter.py b/agent/anthropic_adapter.py index 2cedf51c69d..535f8db8848 100644 --- a/agent/anthropic_adapter.py +++ b/agent/anthropic_adapter.py @@ -1488,8 +1488,9 @@ def run_hermes_oauth_login_pure() -> Optional[Dict[str, Any]]: # Anthropic migrated the OAuth token endpoint to platform.claude.com; # console.anthropic.com now 404s. Try the new host first, then fall # back to console for older deployments (mirrors the refresh path). - # Use the claude-code/ UA prefix: Anthropic blocks claude-cli/ on the - # OAuth token endpoint (returns 404 for all versions). + # UA is _OAUTH_TOKEN_USER_AGENT (a non-claude-code UA) — see the + # constant's definition for why the token endpoint must not send + # claude-code/ (429 UA-prefix block). result = None last_error = None for endpoint in _OAUTH_TOKEN_URLS: diff --git a/tests/agent/test_anthropic_adapter.py b/tests/agent/test_anthropic_adapter.py index f2661470a9d..1393d71ab85 100644 --- a/tests/agent/test_anthropic_adapter.py +++ b/tests/agent/test_anthropic_adapter.py @@ -509,11 +509,20 @@ class TestResolveAnthropicToken: class TestRefreshOauthToken: def test_returns_none_without_refresh_token(self, tmp_path, monkeypatch): monkeypatch.setattr("agent.anthropic_adapter.Path.home", lambda: tmp_path) + # Neutralize live Claude Code sources (macOS Keychain + ~/.claude file) + # so the adopt-already-refreshed branch can't short-circuit with a real + # credential on a dev/CI machine that happens to have Claude Code creds. + monkeypatch.setattr( + "agent.anthropic_adapter.read_claude_code_credentials", lambda: None + ) creds = {"accessToken": "expired", "refreshToken": "", "expiresAt": 0} assert _refresh_oauth_token(creds) is None def test_successful_refresh(self, tmp_path, monkeypatch): monkeypatch.setattr("agent.anthropic_adapter.Path.home", lambda: tmp_path) + monkeypatch.setattr( + "agent.anthropic_adapter.read_claude_code_credentials", lambda: None + ) creds = { "accessToken": "old-token", @@ -547,6 +556,9 @@ class TestRefreshOauthToken: def test_failed_refresh_returns_none(self, tmp_path, monkeypatch): monkeypatch.setattr("agent.anthropic_adapter.Path.home", lambda: tmp_path) + monkeypatch.setattr( + "agent.anthropic_adapter.read_claude_code_credentials", lambda: None + ) creds = { "accessToken": "old", "refreshToken": "refresh-123",