fix(adapter): map persona-friendly slugs (claude-code, anthropic) to registry names
All checks were successful
CI / Adapter unit tests (push) Successful in 1m46s
CI / validate (push) Successful in 7m9s

Phase 4 verification surfaced a follow-up edge case the initial fix missed:
the persona env files use friendlier slugs than the registry's canonical names:
  * MODEL_PROVIDER=claude-code  -> anthropic-oauth (Claude Code subscription)
  * MODEL_PROVIDER=anthropic    -> anthropic-api  (direct Anthropic API key)

Without an alias map, a lead workspace's MODEL_PROVIDER=claude-code env
fell through the slug-detection path; when the YAML didn't pin a
provider, the model-prefix matcher saw MODEL=MiniMax-M2.7 and routed the
lead to MiniMax — even though CLAUDE_CODE_OAUTH_TOKEN was clearly the
intended auth path.

Add _PROVIDER_SLUG_ALIASES with the two operator-facing slugs that don't
match registry names verbatim. The alias map is consulted before the
slug-vs-legacy detection, so claude-code now resolves to anthropic-oauth
and the lead boots through OAuth as intended.

Tests
-----
+ test_persona_env_lead_with_minimax_model_routes_via_oauth — lock in
  the alias-map behavior so a future contributor can't silently re-introduce
  the lead-mis-routed-to-MiniMax bug.
+ test_anthropic_alias_resolves_to_anthropic_api — covers the second
  alias path.

Updated test_persona_env_lead_claude_code_resolves_correctly to assert
the new (correct) behavior: provider == 'anthropic-oauth', not None.

Full adapter suite: 78/78 pass.
This commit is contained in:
claude-ceo-assistant 2026-05-08 14:23:51 -07:00
parent 1742b60e62
commit 16b28b9a65
2 changed files with 73 additions and 12 deletions

View File

@ -278,6 +278,26 @@ def _load_providers(config_path: str) -> tuple:
return tuple(parsed) return tuple(parsed)
# Aliases for `MODEL_PROVIDER` env values that should map to a registry
# provider name. The persona env files use shorter / friendlier slugs
# than the registry's canonical names — without this alias map a value
# like ``MODEL_PROVIDER=claude-code`` would fall through to YAML-based
# resolution and (when the YAML doesn't pin a provider) hit the
# model-prefix matcher with the operator-picked MODEL, mis-routing a
# lead workspace through MiniMax even though its CLAUDE_CODE_OAUTH_TOKEN
# was clearly meant to be used.
#
# Maintain this list in sync with the persona env file convention:
# - ``claude-code`` → ``anthropic-oauth`` (Claude Code subscription path)
# - ``anthropic`` → ``anthropic-api`` (direct Anthropic API key)
# Provider names already in the registry alias to themselves implicitly
# (the ``in registry`` check catches them before this map is consulted).
_PROVIDER_SLUG_ALIASES = {
"claude-code": "anthropic-oauth",
"anthropic": "anthropic-api",
}
def _resolve_model_and_provider_from_env( def _resolve_model_and_provider_from_env(
yaml_model: str, yaml_model: str,
yaml_provider: str, yaml_provider: str,
@ -331,8 +351,20 @@ def _resolve_model_and_provider_from_env(
# (provider name) vs. the legacy convention (model id). Persona- # (provider name) vs. the legacy convention (model id). Persona-
# convention wins when the value matches a registered provider; we # convention wins when the value matches a registered provider; we
# fall back to legacy interpretation only when it doesn't. # fall back to legacy interpretation only when it doesn't.
#
# First, apply the alias map so persona-friendly slugs like
# ``claude-code`` resolve to the canonical registry name
# ``anthropic-oauth``. Without this, a lead workspace's
# ``MODEL_PROVIDER=claude-code`` env would fall through to the model-
# prefix matcher, see ``MODEL=MiniMax-M2.7`` and mis-route to MiniMax
# even though the operator's intent (and the OAuth token they set)
# was the OAuth subscription path.
env_provider_resolved = _PROVIDER_SLUG_ALIASES.get(
env_provider.lower(), env_provider,
) if env_provider else ""
env_provider_is_slug = ( env_provider_is_slug = (
bool(env_provider) and env_provider.lower() in provider_names_lower bool(env_provider_resolved)
and env_provider_resolved.lower() in provider_names_lower
) )
# Picked model resolution # Picked model resolution
@ -345,10 +377,10 @@ def _resolve_model_and_provider_from_env(
else: else:
picked_model = yaml_model or "" picked_model = yaml_model or ""
# Explicit provider resolution — env wins when it's a registered slug, # Explicit provider resolution — env wins when it's a registered slug
# otherwise fall back to YAML. # (after alias mapping), otherwise fall back to YAML.
if env_provider_is_slug: if env_provider_is_slug:
explicit_provider = env_provider explicit_provider = env_provider_resolved
else: else:
explicit_provider = yaml_provider or None explicit_provider = yaml_provider or None

View File

@ -73,10 +73,11 @@ def test_persona_env_minimax_resolves_correctly(monkeypatch):
def test_persona_env_lead_claude_code_resolves_correctly(monkeypatch): def test_persona_env_lead_claude_code_resolves_correctly(monkeypatch):
"""Lead persona env (MODEL=opus, MODEL_PROVIDER=claude-code) — """Lead persona env (MODEL=opus, MODEL_PROVIDER=claude-code) —
``claude-code`` isn't a registered provider name (registry uses ``claude-code`` is the persona-friendly alias for the canonical
``anthropic-oauth``), so it falls back to legacy interpretation ``anthropic-oauth`` registry name. Must resolve via the alias map
and yields no explicit provider, letting the model-based so the lead boots through the OAuth subscription path even when
fall-through to providers[0]=anthropic-oauth do the right thing.""" MODEL is a non-Anthropic model id (e.g. an operator who picked
MiniMax in canvas but whose persona env still pins claude-code)."""
_clear_env(monkeypatch) _clear_env(monkeypatch)
monkeypatch.setenv("MODEL", "opus") monkeypatch.setenv("MODEL", "opus")
monkeypatch.setenv("MODEL_PROVIDER", "claude-code") monkeypatch.setenv("MODEL_PROVIDER", "claude-code")
@ -84,10 +85,38 @@ def test_persona_env_lead_claude_code_resolves_correctly(monkeypatch):
yaml_model="", yaml_provider="", providers=_REGISTRY, yaml_model="", yaml_provider="", providers=_REGISTRY,
) )
assert model == "opus" assert model == "opus"
# claude-code is not a registered slug, so this falls back — # claude-code → anthropic-oauth via the alias map
# provider is None and the caller will model-resolve to assert provider == "anthropic-oauth"
# anthropic-oauth via the alias match on "opus".
assert provider is None
def test_persona_env_lead_with_minimax_model_routes_via_oauth(monkeypatch):
"""Lead workspace whose persona pins MODEL_PROVIDER=claude-code but
whose YAML/canvas selection happens to be a MiniMax model still
routes via OAuth the persona's provider pin wins over the
model-prefix matcher. Without the alias map, the fall-through
mis-routed leads to MiniMax even when their CLAUDE_CODE_OAUTH_TOKEN
was set."""
_clear_env(monkeypatch)
monkeypatch.setenv("MODEL", "MiniMax-M2.7")
monkeypatch.setenv("MODEL_PROVIDER", "claude-code")
model, provider = _resolve_model_and_provider_from_env(
yaml_model="", yaml_provider="", providers=_REGISTRY,
)
assert model == "MiniMax-M2.7"
assert provider == "anthropic-oauth"
def test_anthropic_alias_resolves_to_anthropic_api(monkeypatch):
"""``MODEL_PROVIDER=anthropic`` alias → ``anthropic-api`` (direct
Anthropic API key path)."""
_clear_env(monkeypatch)
monkeypatch.setenv("MODEL", "claude-opus-4-7")
monkeypatch.setenv("MODEL_PROVIDER", "anthropic")
model, provider = _resolve_model_and_provider_from_env(
yaml_model="", yaml_provider="", providers=_REGISTRY,
)
assert model == "claude-opus-4-7"
assert provider == "anthropic-api"
def test_persona_env_glm_resolves_correctly(monkeypatch): def test_persona_env_glm_resolves_correctly(monkeypatch):