From 09693cd3a339d61a0f461e59d0a23b5a33479185 Mon Sep 17 00:00:00 2001 From: kshitijk4poor <82637225+kshitijk4poor@users.noreply.github.com> Date: Sat, 4 Jul 2026 15:10:28 +0530 Subject: [PATCH] fix: complete OAuth-UA salvage follow-up (stale comment + test keychain isolation) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two review findings on the #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.) --- agent/anthropic_adapter.py | 5 +++-- tests/agent/test_anthropic_adapter.py | 12 ++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) 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",