[BUG] Resumed background subagent/teammate loses its spawn-time model override and reverts to parent model
Preflight Checklist
- [x] I have searched existing issues. The closest is #20291 (
/agentsshows "sonnet" for inherit-model subagents) — closed and distinct: that is about the static/agentsdisplay for inherited models. This report is about a runtime model regression on resume of a background subagent/teammate. - [x] This is a single bug report.
- [x] I am using the latest version of Claude Code.
What's Wrong?
When a background subagent (an addressable "teammate" spawned via the Agent/Task tool with run_in_background) is given a spawn-time model override, that override is applied correctly on the first run but is silently dropped when the agent is resumed via SendMessage (resume-by-agentId). On resume, the agent falls back to the parent / main-conversation model instead of the model it was originally spawned with.
Concretely, with the main conversation on Opus 4.8:
| Phase | Requested model | Actual model |
|------|------------------|--------------|
| Initial Agent(subagent_type: "general-purpose", model: "sonnet") | sonnet | sonnet ✅ |
| Resume of that same agent via SendMessage (no model param) | (should stay sonnet) | opus (parent) ❌ |
The per-invocation model override only governs the initial spawn; resumed turns re-resolve the model and, for an agent type without a model: in its .claude/agents/<name>.md frontmatter, drop all the way through to the parent model. This is surprising for long-lived teammates that are meant to be resumed repeatedly — a teammate you deliberately pinned to a cheaper/faster model can silently start running on the parent (more expensive) model after the first turn, with no indication in the UI.
What Should Happen?
A resumed background agent should retain the model it was spawned with. The spawn-time model override should persist across SendMessage resumes for the lifetime of that agentId (or, at minimum, the UI/telemetry should clearly show the resumed model so the change is visible and billable cost is predictable).
Workaround (and the asymmetry that confirms it)
Pinning the model in the agent definition frontmatter instead of the spawn-time param works correctly across resumes:
general-purpose(no frontmattermodel:) + spawn-timemodel: "sonnet"→ reverts to parent (opus) on resume.- A custom agent type with
model: sonnetin.claude/agents/<name>.md→ stays sonnet across resumes ✅.
So the durable fix for users is frontmatter; the bug is that the per-invocation override is not durable across resume, which the model-resolution docs (CLAUDE_CODE_SUBAGENT_MODEL env > per-invocation param > frontmatter > main conversation) do not lead you to expect for a persistent teammate.
Steps to Reproduce
- Start Claude Code with the main model = Opus.
- Spawn a background general-purpose teammate with an explicit Sonnet override:
````
Spawn a background general-purpose agent with model "sonnet" and ask it which model it is running.
→ it correctly reports Sonnet.
- Resume that same agent via SendMessage (by its
agentId), e.g. "say hi again and report your model". - Observe the resumed turn now runs on the parent model (Opus), not the Sonnet it was spawned with.
- For contrast, repeat with an agent type that has
model: sonnetin its.claude/agents/*.mdfrontmatter → it stays Sonnet across resumes.
Error Messages/Logs
# No error — model silently changes between the initial spawn and the resumed turn.
# Initial spawn (model: "sonnet") -> runs sonnet
# SendMessage resume (no model param) -> runs parent model (opus)
Claude Model
Opus (main conversation); teammate spawned with model: "sonnet"
Is this a regression?
I don't know
Claude Code Version
2.1.175 (Claude Code)
Platform
Anthropic API
Operating System
Linux
Additional Information
Related (closed, different surface): #20291. That issue is about the static /agents display for inherited models; this is about the runtime model not persisting across SendMessage resume of a backgrounded, addressable teammate spawned with a per-invocation model override.
Showing cached comments. Read the full discussion on GitHub ↗
3 Comments
Reproduced on 2.1.198 (linux).
Repro:
model: haikuoverride→ first turn ran
claude-haiku-4-5-20251001(per-turnusagein the task output JSONL).SendMessage(same session, ~20s later, well within cache TTL)→ the resumed turn ran on the parent session model instead of the spawn-time override.
Usage data (from the subagent transcript JSONL):
| turn | model | cache_creation | cache_read |
|---|---|---|---|
| spawn | claude-haiku-4-5-20251001 | 20,417 | 0 |
| resume (SendMessage) | parent session model | 21,917 | 0 |
Cost side effect worth flagging: the silent model switch also invalidates the prompt cache
(different model = different cache), so the resumed turn re-creates the entire transcript as
cache_creationat the 1.25x write rate — billed at the parent-tier model price. A control runwith no model override (spawn and resume on the same model) reads cache fine on resume
(
cache_read = 18,663), confirming the cache loss is a consequence of the model regression,not a separate caching bug.
Reproduced independently (Claude Code 2.1.207, Windows 11, VS Code extension), with a controlled A/B that isolates a variable none of the reports above control for: runtime
model:param vs. a custom agent-definition's frontmatter pin.Setup: orchestrator session model = Sonnet. Spawned two async subagents pinned to Haiku, resumed each via SendMessage after completion.
| Path | Turn 1 | Turn 2 (post-resume) |
|---|---|---|
|
subagent_type: general-purpose,model: "haiku"param |claude-haiku-4-5|claude-sonnet-5(flipped to orchestrator's model) ||
subagent_type: <custom>.md,model: haikuin frontmatter |claude-haiku-4-5|claude-haiku-4-5(held) |Same defect as above, but the frontmatter-pinned custom agent type was immune in this test — the bug appears specific to the runtime
model:param's resolution on resume, not the static agent-definition path.Real-world cost impact (separate live session): a
general-purposesubagent spawned withmodel: "sonnet"under a Fable-tier orchestrator ran correctly for 91 messages, then one mid-task follow-up flipped it to Fable for the remaining 51 and it never reverted — ~186M input/cache tokens and ~252K output tokens billed at Fable-tier pricing that should have been Sonnet.Workaround for others hitting this: until fixed, prefer a custom
.claude/agents/<name>.mdwithmodel:frontmatter over the runtimemodel:param for any subagent that might receive a follow-up or get resumed — the frontmatter path held in testing where the param path did not.Fixed in 2.1.211 — changelog: "Fixed subagents spawned with an explicit model override reverting to the parent's model when resumed or sent a follow-up message" (https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md). Closing — reply to reopen if it still happens on the latest version.
🤖 Generated with Claude Code