fix(adapter): keep setup() routing — strip prefix only at CLI invocation

Live-test revealed a regression in PR #24's setup() strip: the wheel-
default `anthropic:claude-opus-4-7` paired with an OAuth workspace
(CLAUDE_CODE_OAUTH_TOKEN set, no ANTHROPIC_API_KEY) is the realistic
production shape. Stripping in setup() routes those users into the
`anthropic-api` provider entry, after which the CLI hangs at
`initialize` because no API key env is set. Caught on workspace
dd40faf8 on 2026-05-01 — banner went `provider=anthropic-api` and
A2A wedged on Control request timeout.

Pre-fix routing (let prefixed strings fall through to providers[0] =
anthropic-oauth) is actually correct for this combo. The strip is only
needed at the CLI invocation site (create_executor) where claude's
`--model` arg must be a bare id.

Tests: replace `test_setup_strip_routes_prefixed_anthropic_to_anthropic_api`
with `test_setup_keeps_prefix_routing_oauth_for_anthropic_prefix`,
which pins the inverse — prefixed model + OAuth env stays on oauth and
emits no API-key warning. The 5 unit cases on `_strip_provider_prefix`
plus the `create_executor` strip pins remain unchanged. 36/36 pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Hongming Wang 2026-05-01 16:46:54 -07:00
parent 4d5e85f3a0
commit a78626ced4
2 changed files with 34 additions and 9 deletions

View File

@ -311,7 +311,14 @@ class ClaudeCodeAdapter(BaseAdapter):
picked_model = rc.get("model") or "sonnet"
else:
picked_model = getattr(rc, "model", None) or "sonnet"
picked_model = _strip_provider_prefix(picked_model)
# NOTE: do NOT strip the provider prefix here. The pre-fix routing
# behavior — `anthropic:claude-opus-4-7` falls through to
# providers[0] (anthropic-oauth) when no model_prefixes match — is
# actually correct for OAuth users (the realistic case for the
# wheel default). Stripping in setup() routes OAuth users into
# `anthropic-api` provider and the CLI then hangs at `initialize`
# because ANTHROPIC_API_KEY isn't set. The strip belongs only at
# the CLI invocation site (create_executor below).
provider = _resolve_provider(picked_model, providers)
auth_env_options = provider["auth_env"]

View File

@ -877,18 +877,28 @@ async def test_create_executor_strips_anthropic_prefix_dataclass(
@pytest.mark.asyncio
async def test_setup_strip_routes_prefixed_anthropic_to_anthropic_api(
async def test_setup_keeps_prefix_routing_oauth_for_anthropic_prefix(
adapter, monkeypatch, configs_dir, caplog
):
"""With the prefix intact, `anthropic:claude-opus-4-7` doesn't match
anthropic-api's model_prefixes=("claude-",) and falls back to
anthropic-oauth wrong for users on ANTHROPIC_API_KEY. The strip in
setup() must run BEFORE _resolve_provider so routing sees the bare id.
"""The wheel default `anthropic:claude-opus-4-7` paired with an OAuth
workspace (CLAUDE_CODE_OAUTH_TOKEN set, no ANTHROPIC_API_KEY) is the
realistic production shape. setup() must NOT strip the prefix doing
so makes `_resolve_provider` match anthropic-api's `claude-` prefix
and route the user away from oauth, after which the CLI hangs at
`initialize` because no API key is set. Caught live on workspace
dd40faf8 on 2026-05-01: the first prefix-strip pass in setup()
routed banner to `provider=anthropic-api`, OAuth user wedged.
Pin: leave routing untouched. The strip lives only in
create_executor() so the CLI gets a bare id. _resolve_provider's
fallback (providers[0] = anthropic-oauth) correctly serves the
OAuth + wheel-default combo.
"""
import logging
monkeypatch.delenv("ANTHROPIC_BASE_URL", raising=False)
monkeypatch.setenv("ANTHROPIC_API_KEY", "sk-test")
monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False)
monkeypatch.setenv("CLAUDE_CODE_OAUTH_TOKEN", "oat-test")
cfg = _StubAdapterConfig(
runtime_config={"model": "anthropic:claude-opus-4-7"},
config_path=configs_dir,
@ -902,6 +912,14 @@ async def test_setup_strip_routes_prefixed_anthropic_to_anthropic_api(
if "Claude Code adapter starting" in r.getMessage()),
"",
)
assert "provider=anthropic-api" in banner, (
f"Expected provider=anthropic-api after stripping prefix; banner={banner!r}"
assert "provider=anthropic-oauth" in banner, (
f"Prefixed model with OAuth env must keep oauth routing; banner={banner!r}"
)
# And the no-API-key warning must NOT fire (OAuth env satisfies oauth).
auth_warnings = [
r.getMessage() for r in caplog.records
if "AuthenticationError" in r.getMessage()
]
assert not auth_warnings, (
f"OAuth users should not see API-key warnings; got {auth_warnings}"
)