From 808fee151d42b77a763ea4a8ec711d7b501cece6 Mon Sep 17 00:00:00 2001 From: nftpoetrist Date: Mon, 4 May 2026 00:12:58 +0300 Subject: [PATCH] fix(auxiliary): propagate explicit_api_key to _try_anthropic() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _try_anthropic() lacked the explicit_api_key parameter added to _try_openrouter() in #18768. When resolve_provider_client() is called with provider="anthropic" and an explicit key (e.g. from a fallback_model entry with api_key set), the key was silently ignored — _try_anthropic() always fell back to resolve_anthropic_token(), so the fallback returned None,None for users without a default Anthropic credential configured. Fix: add explicit_api_key: str = None to _try_anthropic() and use explicit_api_key or in both the pool-present and no-pool paths. Pass explicit_api_key=explicit_api_key at the call site in resolve_provider_client(). Symmetric with the _try_openrouter() fix. No behavior change when explicit_api_key is None. --- agent/auxiliary_client.py | 8 ++--- tests/agent/test_auxiliary_client.py | 50 ++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/agent/auxiliary_client.py b/agent/auxiliary_client.py index b86f78f8ec..0c688d23dc 100644 --- a/agent/auxiliary_client.py +++ b/agent/auxiliary_client.py @@ -1529,7 +1529,7 @@ def _build_codex_client(model: str) -> Tuple[Optional[Any], Optional[str]]: return CodexAuxiliaryClient(real_client, model), model -def _try_anthropic() -> Tuple[Optional[Any], Optional[str]]: +def _try_anthropic(explicit_api_key: str = None) -> Tuple[Optional[Any], Optional[str]]: try: from agent.anthropic_adapter import build_anthropic_client, resolve_anthropic_token except ImportError: @@ -1539,10 +1539,10 @@ def _try_anthropic() -> Tuple[Optional[Any], Optional[str]]: if pool_present: if entry is None: return None, None - token = _pool_runtime_api_key(entry) + token = explicit_api_key or _pool_runtime_api_key(entry) else: entry = None - token = resolve_anthropic_token() + token = explicit_api_key or resolve_anthropic_token() if not token: return None, None @@ -2336,7 +2336,7 @@ def resolve_provider_client( if pconfig.auth_type == "api_key": if provider == "anthropic": - client, default_model = _try_anthropic() + client, default_model = _try_anthropic(explicit_api_key=explicit_api_key) if client is None: logger.warning("resolve_provider_client: anthropic requested but no Anthropic credentials found") return None, None diff --git a/tests/agent/test_auxiliary_client.py b/tests/agent/test_auxiliary_client.py index c57a0b6372..43125554df 100644 --- a/tests/agent/test_auxiliary_client.py +++ b/tests/agent/test_auxiliary_client.py @@ -1893,3 +1893,53 @@ class TestOpenRouterExplicitApiKey: assert call_kwargs["api_key"] == "env-fallback-key", ( f"Expected env fallback key to be used when explicit_api_key is None, got: {call_kwargs['api_key']}" ) + + +class TestAnthropicExplicitApiKey: + """Test that explicit_api_key is correctly propagated to _try_anthropic(). + + Parity with the OpenRouter fix in #18768: resolve_provider_client() passes + explicit_api_key to _try_openrouter(), but the anthropic branch was not + updated — _try_anthropic() always fell back to resolve_anthropic_token() + even when an explicit key was supplied (e.g. from a fallback_model entry). + """ + + def test_try_anthropic_uses_explicit_api_key_over_env(self): + """_try_anthropic(explicit_api_key) must use the supplied key, not the env fallback.""" + with patch("agent.anthropic_adapter.resolve_anthropic_token", return_value="env-fallback-key"), \ + patch("agent.anthropic_adapter.build_anthropic_client") as mock_build, \ + patch("agent.auxiliary_client._select_pool_entry", return_value=(False, None)): + mock_build.return_value = MagicMock() + from agent.auxiliary_client import _try_anthropic + client, model = _try_anthropic("explicit-pool-key") + assert client is not None + assert mock_build.call_args.args[0] == "explicit-pool-key", ( + f"Expected explicit_api_key to be passed, got: {mock_build.call_args.args[0]}" + ) + assert mock_build.call_args.args[0] != "env-fallback-key" + + def test_try_anthropic_without_explicit_key_falls_back_to_resolve(self): + """Without explicit_api_key, _try_anthropic falls back to resolve_anthropic_token.""" + with patch("agent.anthropic_adapter.resolve_anthropic_token", return_value="env-fallback-key"), \ + patch("agent.anthropic_adapter.build_anthropic_client") as mock_build, \ + patch("agent.auxiliary_client._select_pool_entry", return_value=(False, None)): + mock_build.return_value = MagicMock() + from agent.auxiliary_client import _try_anthropic + client, model = _try_anthropic() + assert client is not None + assert mock_build.call_args.args[0] == "env-fallback-key" + + def test_resolve_provider_client_passes_explicit_api_key_to_anthropic(self): + """resolve_provider_client(provider='anthropic', explicit_api_key=...) must propagate the key.""" + with patch("agent.anthropic_adapter.resolve_anthropic_token", return_value="env-key"), \ + patch("agent.anthropic_adapter.build_anthropic_client") as mock_build, \ + patch("agent.auxiliary_client._select_pool_entry", return_value=(False, None)): + mock_build.return_value = MagicMock() + client, model = resolve_provider_client( + provider="anthropic", + explicit_api_key="explicit-fallback-key", + ) + assert client is not None + assert mock_build.call_args.args[0] == "explicit-fallback-key", ( + "resolve_provider_client must forward explicit_api_key to _try_anthropic()" + )