Workflow agent() model silently replaced by CLAUDE_CODE_SUBAGENT_MODEL when the id is not in the gateway-discovered model list

Status Open
Reported on v2.1.245
Maintainer reply None cached
Activity 0 comments · opened Aug 27, 2026

Workflow agent() model silently replaced by CLAUDE_CODE_SUBAGENT_MODEL when the id is not in the gateway-discovered model list

Environment

  • Claude Code 2.1.245 (also observed on 2.1.226), macOS (darwin arm64)
  • Custom gateway: ANTHROPIC_BASE_URL pointed at a self-hosted Anthropic-wire proxy, ANTHROPIC_AUTH_TOKEN set, CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1
  • CLAUDE_CODE_SUBAGENT_MODEL set to a gateway-advertised id (claude-delegate-capable)

What happens

With gateway model discovery enabled, Claude Code fetches GET /v1/models from the gateway and treats the returned list as the exhaustive vocabulary for subagent model resolution (Workflow agent() model: opts). Any requested model id absent from that list is silently replaced with CLAUDE_CODE_SUBAGENT_MODEL before the request leaves the machine:

  • No error, no warning, no UI indication at call time.
  • The subagent's agent-*.meta.json records only the requested id (requestedModel), which actively misleads post-hoc inspection — the transcript's served model is the only local trace, and nothing correlates the two.
  • The substituted request reaches the wire as the fallback model. Verified at the gateway with a live SSE tap of per-request events: agents whose agent() opts requested claude-fable-5, claude-fable-5[1m], and (in a later repro) a class id added to the gateway after session start all arrived as model_requested=claude-delegate-capable.

The main-loop model is unaffected (passes through verbatim); only subagent resolution is gated this way.

Reproduction

  1. Point Claude Code at any Anthropic-compatible gateway whose /v1/models returns a partial model list (e.g. it omits claude-opus-5 while happily routing it on /v1/messages). Set CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1 and a CLAUDE_CODE_SUBAGENT_MODEL that is advertised.
  2. Run a Workflow with agent('say hi', {model: 'claude-opus-5'}) (any valid id not in the discovery list).
  3. Observe on the gateway that the request arrives with the CLAUDE_CODE_SUBAGENT_MODEL value instead; observe locally that no error or warning was emitted and the agent meta records the requested id.

A second wrinkle makes this sharper: the discovery list appears to be fetched once per session. A model added to the gateway's /v1/models after session start keeps being silently substituted for the rest of that session, so even a "fixed" gateway doesn't stop the behavior for running sessions.

Impact

A multi-day silent failure in our case: an adversarial-review stage explicitly specified as "strongest model" ran on a different (cheaper) model class for three days across three sessions. Nothing surfaced it; diagnosis required tapping the gateway's event stream and hand-correlating subagent transcripts. The failure mode is the worst kind for model-quality work — outputs remain plausible, so nothing looks wrong.

Expected behavior

Gating subagent models on the discovered list is a defensible safety choice (an unknown id might 404). The defect is the silence. Any of these would be fine:

  1. Refuse: fail the agent() call with a clear error naming the unadvertised id, or
  2. Honor: pass the id through and let the gateway decide (it may route ids it doesn't advertise), or
  3. Substitute loudly: keep the fallback but emit a visible warning and record both requested and resolved ids in the subagent metadata.

Silent substitution converts a configuration gap into invisible model downgrades.

View original on GitHub ↗