Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions run_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -5193,6 +5193,16 @@ def _try_refresh_anthropic_client_credentials(self) -> bool:
_base = getattr(self, "_anthropic_base_url", "") or ""
if "azure.com" in _base:
return False
# A session on a regular Console API key (sk-ant-api…) has nothing to
# refresh — API keys never expire. Re-resolving here would let
# resolve_anthropic_token() prefer Claude Code OAuth credentials
# (~/.claude/.credentials.json, priority 3) over the explicitly
# selected API key (priority 5), silently switching the user's billing
# identity after a transient 429/401 (#75641). Only OAuth/setup-token
# sessions may be refreshed.
from agent.anthropic_adapter import _is_oauth_token as _is_oauth_shape
if not _is_oauth_shape(getattr(self, "_anthropic_api_key", "") or ""):
return False

try:
from agent.anthropic_adapter import resolve_anthropic_token, build_anthropic_client
Expand Down
17 changes: 14 additions & 3 deletions tests/run_agent/test_run_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -5560,7 +5560,17 @@ class TestOAuthFlagAfterCredentialRefresh:
"""_is_anthropic_oauth must update when token type changes during refresh."""

def test_oauth_flag_updates_api_key_to_oauth(self, agent):
"""Refreshing from API key to OAuth token must set flag to True."""
"""API-key session must NOT refresh into OAuth credentials (#75641).

``_try_refresh_anthropic_client_credentials`` used to call
``resolve_anthropic_token()`` unconditionally, 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. A plain API key never expires, so there is
nothing to refresh; the refresh must be a no-op for API-key sessions.
"""
agent.api_mode = "anthropic_messages"
agent.provider = "anthropic"
agent._anthropic_api_key = "sk-ant-api-old"
Expand All @@ -5575,8 +5585,9 @@ def test_oauth_flag_updates_api_key_to_oauth(self, agent):
):
result = agent._try_refresh_anthropic_client_credentials()

assert result is True
assert agent._is_anthropic_oauth is True
# The API-key session must be preserved — no refresh, no OAuth swap.
assert result is False
assert agent._is_anthropic_oauth is False

def test_oauth_flag_updates_oauth_to_api_key(self, agent):
"""Refreshing from OAuth to API key must set flag to False."""
Expand Down
Loading