CLAUDE_CODE_SUBAGENT_MODEL silently discards explicit per-call subagent model since v2.1.223; documented warning never fires; subagent metadata records requested (not effective) model
Summary
Since v2.1.223, when CLAUDE_CODE_SUBAGENT_MODEL is set (via org managed settings env), explicit per-call subagent model requests are silently discarded — every subagent runs the env-pinned model instead. Through v2.1.220 the same configuration honored per-call model params and parent-model inheritance. Additionally, the subagent .meta.json records the requested model rather than the effective one, and the warning described in the v2.1.223 changelog never appears.
Environment
- Claude Code 2.1.226 (regression window pinned to 2.1.220 → 2.1.223, see below)
- macOS (darwin 24.6.0)
- Org managed settings (
/Library/Application Support/ClaudeCode/managed-settings.json) setenv.CLAUDE_CODE_SUBAGENT_MODEL=sonnetandenv.ANTHROPIC_MODEL=opus(file unchanged since May; predates the regression by ~3 months) - Session model set to Fable via
/model
Repro
- Have
CLAUDE_CODE_SUBAGENT_MODEL=sonnetset via managed-settingsenv. - In a session, spawn a subagent with an explicit override — Agent tool
model: "opus"(or"fable"/"haiku"), prompt: "state the exact model ID from your system prompt". - The subagent replies
claude-sonnet-5; its transcript's assistant messages confirm"model": "claude-sonnet-5"on every API response. - The subagent's
.meta.jsonsays"model": "opus"(the requested value). - No warning appears in the tool result, the parent transcript, or the subagent transcript.
Same behavior for Workflow-tool agent() calls with model opts: a run requesting a mix of fable/opus/sonnet served 100% claude-sonnet-5 while the per-agent metadata recorded the requested values.
Regression evidence
Audit of all workflow-run transcripts on one machine (unchanged managed settings throughout), comparing requested model (meta.json) vs served model (message.model on assistant messages):
| Date | CC version (from transcripts) | Requested | Served |
|---|---|---|---|
| Jul 16 – Aug 3 | ≤ 2.1.220 | fable / opus / haiku / sonnet mixes | exactly as requested (claude-fable-5, claude-opus-4-8, claude-opus-5, claude-haiku-4-5-20251001, claude-sonnet-5); agents with no override inherited the parent model |
| Aug 6 onward | 2.1.223, 2.1.226 | fable / opus / haiku / sonnet mixes | 100% claude-sonnet-5 regardless of request; parent-model inheritance also gone |
Changelog cross-reference
The v2.1.223 changelog says: "Added a warning when workflow agents, forked skills, slash commands, or resumed background agents' requested subagent model is restricted and the parent model runs instead."
Observed behavior differs on both counts:
- No warning is emitted anywhere user-visible (checked tool results, parent transcript, subagent transcripts, workflow journal).
- The fallback is not the parent model — the parent session was
claude-fable-5; subagents gotclaude-sonnet-5, i.e. theCLAUDE_CODE_SUBAGENT_MODELvalue.
Expected
Whichever precedence is intended, two things seem clearly wrong today:
- If the env pin is meant to be a hard override (managed-settings policy), the documented warning should actually fire in the tool result / UI so users know their
modelparam was discarded — silent downgrade makes users believe they got the requested model. .meta.jsonshould record the effective model (or both requested and effective). Recording only the requested value makes run audits actively misleading — the metadata saysfablewhile every API call was served bysonnet.
And if the env var was meant to remain a default (the ≤2.1.220 behavior), then explicit per-call model params should win, as the Agent tool schema implies ("Takes precedence over the agent definition's model frontmatter" — no mention that an env pin beats an explicit request).
Impact
Multi-agent workflows that deliberately route hard reasoning to bigger models are silently downgraded wholesale, with metadata that hides it. We only caught this because the workflow progress UI (which reads the live API traffic) disagreed with the metadata.
🤖 Generated with Claude Code
3 Comments
Adding a related failure mode when the env-pin value is the literal string
inheritrather than a real model ID.Environment
AI_AGENT=claude-code/2.1.121/agent(the actual executing process, fromenv)claude --versionreports2.1.239~/.claude/settings.jsonenv.CLAUDE_CODE_SUBAGENT_MODEL=inheritopus[1m]Repro
env.CLAUDE_CODE_SUBAGENT_MODEL=inheritin~/.claude/settings.json.Agent({subagent_type: "Explore", model: "sonnet", prompt: ...})orAgent({subagent_type: "general-purpose", model: "opus", prompt: ...}).> There's an issue with the selected model (inherit). It may not exist or you may not have access to it. Run /model to pick a different model.
total_tokens: 0, tool_uses: 0, duration_ms: 762— the subagent never runs.Reproduced 6 out of 6 attempts across both
Exploreandgeneral-purposebuilt-in subagent types with explicitmodel: "sonnet"andmodel: "opus"overrides in the same session.What this adds to #85592
modelsilently discarded in favour of the env pin) also fires when the pin value is"inherit"— but here the failure is a hard user-visible error, not a silent downgrade, because the CLI's model resolver never rewrites the literal string"inherit"into the parent model ID before the API call.CLAUDE_CODE_SUBAGENT_MODEL=inheritunusable as a workaround for #85592: users who tried setting it toinheritspecifically to stop the env pin overriding per-call model (see e.g. custom PreToolUse gates that flipped fromsonnettoinheritfor exactly this reason) get every subagent call hard-failing instead..meta.jsonshows the requested model, the tool_result shows the resolver error naminginherit, and neither surfaces the fact that the env pin is the actual culprit.Suggested fix scope
Either:
"inherit"to the parent model ID inside the resolver (matches the schema's documented "inherits from the parent" behaviour), and honour explicit per-callmodelover the env pin; or"inherit"at settings-load time with a clear error message, so users don't hit the resolver failure at first Agent call.Happy to test a fix against 2.1.239 if useful.
Adding empirical data plus links to the specific docs this contradicts.
Data:
CLAUDE_CODE_SUBAGENT_MODEL=claude-haiku-4-5-20251001exported globally via shell profile (not managed-settings). Two agents in.claude/agents/declaremodel: sonnetin frontmatter:coder-scoutandcoder-build. Checked 4 real invocations (nomodelparam passed at call time, per each subagent'smeta.json), greppingmessage.modelacross every assistant turn:claude-haiku-4-5-20251001claude-haiku-4-5-20251001561/561 turns served by the env-pinned model despite explicit
model: sonnetfrontmatter.This matches the documented precedence at code.claude.com/docs/en/sub-agents ("Configure subagents → Choose a model"): env var > per-invocation param > frontmatter > main conversation model. So against that page, this is working as designed.
But it contradicts two other places:
modelparameter description (in-product tool schema, no public URL): "Takes precedence over the agent definition's model frontmatter." No mention that a machine-wide env var can override the param itself.## 2.1.223: "Added a warning when workflow agents, forked skills, slash commands, or resumed background agents' requested subagent model is restricted and the parent model runs instead." Here the env-pinned model runs, not the parent model, and no warning fires anywhere (tool result, transcript, ormeta.json, which still records the requested model, not the effective one).Whichever precedence is intended, the tool schema text and changelog description should match actual behavior, and the promised warning should fire so agent authors know their
model:field is inert.Thanks for the very detailed report — I ran this against the latest release (2.1.233) and can confirm parts of it, with one important correction on the regression framing.
Confirmed on 2.1.233:
CLAUDE_CODE_SUBAGENT_MODELset, an Agent call with an explicitmodelparameter is served the env-pinned model (verified via the subagent's self-reported model ID and"model"on its transcript's assistant messages)..meta.jsonrecords the requested model, not the effective one.Not a precedence regression: I ran the identical test on 2.1.220 and the env pin overrides the explicit
modelparameter there too. This precedence is documented: the settings docs describeCLAUDE_CODE_SUBAGENT_MODELas overriding the per-invocationmodelparameter and subagent frontmatter (withinheritrestoring normal resolution).Why it changed for you at 2.1.223: that release fixed machine-local
managed-settings.jsonenvblocks being disabled when server-delivered settings are present (see the 2.1.223 changelog). Your May-era env pin most likely only started taking effect then — which matches your ≤2.1.220 observations of per-call models being honored.Why the documented warning doesn't fire: the 2.1.223 warning entry covers org-restricted subagent models falling back to the parent model — a different mechanism than the env pin, which currently substitutes silently.
Keeping this open for the two things that are genuinely wrong regardless of intended precedence: the silent substitution of an explicitly requested model should surface a notice, and subagent metadata should record the effective model (or both).
🤖 Generated with Claude Code