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

Status Open
Reported on v2.1.226
Maintainer reply ✓ Yes — bcherny
Activity 3 comments · opened Aug 10, 2026
💡 Likely answer: A maintainer (bcherny, collaborator) responded on this thread — see the highlighted reply below.

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) set env.CLAUDE_CODE_SUBAGENT_MODEL=sonnet and env.ANTHROPIC_MODEL=opus (file unchanged since May; predates the regression by ~3 months)
  • Session model set to Fable via /model

Repro

  1. Have CLAUDE_CODE_SUBAGENT_MODEL=sonnet set via managed-settings env.
  2. 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".
  3. The subagent replies claude-sonnet-5; its transcript's assistant messages confirm "model": "claude-sonnet-5" on every API response.
  4. The subagent's .meta.json says "model": "opus" (the requested value).
  5. 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 got claude-sonnet-5, i.e. the CLAUDE_CODE_SUBAGENT_MODEL value.

Expected

Whichever precedence is intended, two things seem clearly wrong today:

  1. 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 model param was discarded — silent downgrade makes users believe they got the requested model.
  2. .meta.json should record the effective model (or both requested and effective). Recording only the requested value makes run audits actively misleading — the metadata says fable while every API call was served by sonnet.

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

View original on GitHub ↗

3 Comments

adwbot · 8 days ago

Adding a related failure mode when the env-pin value is the literal string inherit rather than a real model ID.

Environment

  • AI_AGENT=claude-code/2.1.121/agent (the actual executing process, from env)
  • claude --version reports 2.1.239
  • ~/.claude/settings.json env.CLAUDE_CODE_SUBAGENT_MODEL=inherit
  • Session model: opus[1m]
  • macOS 25.6.0

Repro

  1. Set env.CLAUDE_CODE_SUBAGENT_MODEL=inherit in ~/.claude/settings.json.
  2. Dispatch Agent({subagent_type: "Explore", model: "sonnet", prompt: ...}) or Agent({subagent_type: "general-purpose", model: "opus", prompt: ...}).
  3. The Agent tool returns the tool_result:

> 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 Explore and general-purpose built-in subagent types with explicit model: "sonnet" and model: "opus" overrides in the same session.

What this adds to #85592

  1. The bug from #85592 (per-call model silently 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.
  2. This makes CLAUDE_CODE_SUBAGENT_MODEL=inherit unusable as a workaround for #85592: users who tried setting it to inherit specifically to stop the env pin overriding per-call model (see e.g. custom PreToolUse gates that flipped from sonnet to inherit for exactly this reason) get every subagent call hard-failing instead.
  3. Combined with the metadata bug you flagged, this is a debugging trap: .meta.json shows the requested model, the tool_result shows the resolver error naming inherit, and neither surfaces the fact that the env pin is the actual culprit.

Suggested fix scope

Either:

  • resolve "inherit" to the parent model ID inside the resolver (matches the schema's documented "inherits from the parent" behaviour), and honour explicit per-call model over the env pin; or
  • reject "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.

clouatre · 6 days ago

Adding empirical data plus links to the specific docs this contradicts.

Data: CLAUDE_CODE_SUBAGENT_MODEL=claude-haiku-4-5-20251001 exported globally via shell profile (not managed-settings). Two agents in .claude/agents/ declare model: sonnet in frontmatter: coder-scout and coder-build. Checked 4 real invocations (no model param passed at call time, per each subagent's meta.json), grepping message.model across every assistant turn:

  • coder-scout x3: 58, 281, 44 turns, 100% claude-haiku-4-5-20251001
  • coder-build x1: 178 turns, 100% claude-haiku-4-5-20251001

561/561 turns served by the env-pinned model despite explicit model: sonnet frontmatter.

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:

  1. The Agent tool's own model parameter 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. CHANGELOG.md, ## 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, or meta.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.

bcherny collaborator · 5 days ago

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:

  • With CLAUDE_CODE_SUBAGENT_MODEL set, an Agent call with an explicit model parameter is served the env-pinned model (verified via the subagent's self-reported model ID and "model" on its transcript's assistant messages).
  • No warning appears anywhere (tool result, parent transcript, subagent transcript).
  • The subagent's .meta.json records 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 model parameter there too. This precedence is documented: the settings docs describe CLAUDE_CODE_SUBAGENT_MODEL as overriding the per-invocation model parameter and subagent frontmatter (with inherit restoring normal resolution).

Why it changed for you at 2.1.223: that release fixed machine-local managed-settings.json env blocks 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