[BUG] opusplan/haiku in plan mode: subagents (e.g. Explore) inherit plan-upgrade model instead of their defined model
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
With the opusplan model alias in plan mode, every spawned subagent — including the built-in Explore agent whose definition is model: "haiku" — runs on claude-opus-4-8 instead of claude-haiku-4-5-20251001. Per-invocation model picks on the Agent tool are silently overridden the same way.
The same mechanism affects the haiku alias: in plan mode its subagents run on sonnet.
Reproduction matrix (CC 2.1.175, verified on both interactive TUI and headless claude -p):
| alias | permissionMode | parent model | Explore subagent model | OK? |
|---|---|---|---|---|
| opus | default | opus-4-8 | haiku-4-5 | OK |
| opus[1m] | default / plan | opus-4-8 | haiku-4-5 | OK |
| sonnet / sonnet[1m] | default / plan | sonnet-4-6 | haiku-4-5 | OK |
| opusplan | default | sonnet-4-6 | haiku-4-5 | OK |
| opusplan | plan | opus-4-8 | opus-4-8 | BUG |
| opusplan[1m] | plan | opus-4-8 | opus-4-8 | BUG |
| haiku | default | haiku-4-5 | haiku-4-5 | OK |
| haiku | plan | sonnet-4-6 | sonnet-4-6 | BUG |
The discriminator is the alias alone: same binary, same session, only the two mode-dependent aliases (opusplan, haiku) break. Counterintuitively, opusplan — the alias designed to save cost — is more expensive in plan mode than plain opus, which routes Explore to haiku correctly (~5x cost difference: Haiku 4.5 $1/$5 vs Opus 4.8 $5/$25 per MTok).
What Should Happen?
The Explore subagent should run on claude-haiku-4-5-20251001 as set by its built-in definition (model: "haiku"), regardless of which alias the parent session uses. Per-invocation model parameters on the Agent tool should also be honored. The opusplan plan-mode upgrade should apply only to the main conversation loop, not to subagent queries.
Error Messages/Logs
# claude --debug-to-stderr --model opusplan --permission-mode plan -p "Use the Explore agent to list files..."
# All API requests dispatch on opus, including the Explore subagent's:
2026-06-12T14:09:19.220Z [DEBUG] [API REQUEST] /v1/messages x-client-request-id=f31a57ab-... source=agent:builtin:Explore
2026-06-12T14:09:23.992Z [DEBUG] [API REQUEST] /v1/messages x-client-request-id=581700f8-... source=agent:builtin:Explore
# grep "dispatching to firstParty model=" -> 4x "claude-opus-4-8[1m]", zero "haiku" occurrences in the entire log.
# Notably ABSENT: the warning "Subagent model \"haiku\" is not in the availableModels allowlist; inheriting the parent model instead"
# -> the subagent model resolver succeeded; the override happens downstream (see Additional Information).
Steps to Reproduce
- Run (correct behavior, for contrast):
``bash`
claude --model "opus[1m]" --permission-mode plan \
-p "Use the Explore agent to list files in the current directory."
~/.claude/projects/<proj>/<session-id>/subagents/agent-*.jsonl
Check -> .message.model = claude-haiku-4-5-20251001` (correct)
- Run (bug):
``bash`
claude --model opusplan --permission-mode plan \
-p "Use the Explore agent to list files in the current directory."
.message.model
Same check -> = claude-opus-4-8` (wrong)
- Same result in interactive mode:
/model opusplan+ plan mode + any prompt that spawns the Explore agent.
- Variant:
--model haiku --permission-mode plan-> Explore runs onclaude-sonnet-4-6(wrong)
Claude Model
Other
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.1.175
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
iTerm2
Additional Information
Root cause (from reading the bundled JS in the 2.1.175 binary):
The runtime model getter (getRuntimeMainLoopModel, minified as mR) re-reads the session-global alias setting (getUserSpecifiedModelSetting(), minified Xl) on every call and applies the plan-mode upgrade unconditionally whenever the session alias is opusplan/haiku and the permission mode is plan:
// actual minified code, CC 2.1.175, reformatted:
function mR(H) {
let {permissionMode: _, mainLoopModel: q, exceeds200kTokens: K = !1} = H,
O = Xl(); // <- GLOBAL session alias, e.g. "opusplan"
if ((O === "opusplan" || O === "opusplan[1m]") && _ === "plan" && !K) {
let $ = O === "opusplan[1m]" || PM() ? qF(MJ()) : MJ(); // opus(-[1m])
if (!(KI($) ?? o4($))) { /* allowlist warn */ return M9(O); }
return $; // <- opus, regardless of `mainLoopModel` arg
}
if (Xl() === "haiku" && _ === "plan") {
let z = wG(); // sonnet
if (!(KI(z) ?? o4(z))) { /* allowlist warn */ return M9("haiku"); }
return z; // <- sonnet, regardless of `mainLoopModel` arg
}
return q; // other aliases: pass through
}
The subagent model resolver (Le) works correctly: for Explore it resolves the definition model:"haiku" to the haiku model ID (precedence env > per-invocation > definition > inherit), and that becomes the subagent's options.mainLoopModel.
But the query loop — shared by parent and subagent sessions — recomputes the effective API model through mR at query-setup time:
// between a3("query_setup_start") ... a3("query_setup_end"):
IH = I8(x).mode, // permission mode, inherited from parent context: "plan"
dH = mR({permissionMode: IH, mainLoopModel: S ?? v[C] ?? E, exceeds200kTokens: uH}),
For a subagent this is mR({permissionMode: "plan", mainLoopModel: <haiku>}) — but Xl() inside still returns "opusplan" (it is session-global), so the opusplan branch fires and returns opus. The correctly-resolved haiku model is discarded. The [1m] suffix observed in dispatch logs is itself a fingerprint of this branch (PM() ? qF(MJ()) : MJ()) — nothing in the Le path appends [1m] to a haiku resolution.
opus/opus[1m]/sonnet have no branch in mR, fall through to return q, and subagents keep their intended model — matching the observed matrix exactly.
Suggested fix (any of):
- In the query loop, skip the
Xl()-based upgrade branches when the context is a subagent (the resolvedmainLoopModelalready encodes the right answer), or - Have
mRtrust itsmainLoopModelargument whenever it differs from the session's resting model (i.e., it was already explicitly resolved), or - Spawn subagents with their own permission mode rather than inheriting
"plan".
Workaround: use opus[1m] (or plain opus) instead of opusplan.
Related: #29768 (closed as not planned) — same symptom reported earlier without root cause isolation. This report narrows it to the mode-dependent aliases (opusplan, haiku) and pins the exact mechanism; plain aliases are unaffected.
4 Comments
Re-tested on CC 2.1.185 (2026-06-22) — still reproduces
Ran the full behavioural matrix headless (
claude -p, scratchpad dir), extracting.message.modelfrom subagent transcripts (subagents/agent-*.jsonl):| alias | permissionMode | Explore
.message.model| expected | result ||---|---|---|---|---|
|
opusplan| plan | claude-opus-4-8 | claude-haiku-4-5-20251001 | ✗ ||
haiku| plan | claude-sonnet-4-6 | claude-haiku-4-5-20251001 | ✗ ||
opus[1m]| plan | claude-haiku-4-5-20251001 | claude-haiku-4-5-20251001 | ✓ ||
sonnet| plan | claude-haiku-4-5-20251001 | claude-haiku-4-5-20251001 | ✓ ||
opusplan| default | claude-haiku-4-5-20251001 | claude-haiku-4-5-20251001 | ✓ |Binary confirmation (2.1.185): The
mRfunction has been renamed toc0andXl()toj7(), but the logic is structurally identical — the plan-mode upgrade branches still read the session-global alias instead of the passedmainLoopModel. Reconstructed from binary:Workarounds (still valid): Use
opus[1m]instead ofopusplan— no plan-mode branch inc0, subagents get their intended model. Or setCLAUDE_CODE_SUBAGENT_MODEL=haikubefore launching.Re-test on CC 2.1.191 — full 32-cell matrix
Ran a full headless sweep on 2.1.191 (macOS, Darwin 25.5.0, Anthropic API): 8 aliases × 4 permission modes = 32 cells, measuring the Explore subagent's
.message.modelfrom the session JSONL ($PROJ/$sid/subagents/agent-*.jsonl).Verdict: #67942 still reproduces on 2.1.191. 29 OK / 3 BUG / 0 inconclusive.
Full matrix
| alias | mode | parent model | Explore model | expected | verdict |
|---|---|---|---|---|---|
| opus | default | claude-opus-4-8 | claude-haiku-4-5-20251001 | claude-haiku-4-5-20251001 | ✅ OK |
| opus | plan | claude-opus-4-8 | claude-haiku-4-5-20251001 | claude-haiku-4-5-20251001 | ✅ OK |
| opus | acceptEdits | claude-opus-4-8 | claude-haiku-4-5-20251001 | claude-haiku-4-5-20251001 | ✅ OK |
| opus | bypassPermissions | claude-opus-4-8 | claude-haiku-4-5-20251001 | claude-haiku-4-5-20251001 | ✅ OK |
| opus[1m] | default | claude-opus-4-8 | claude-haiku-4-5-20251001 | claude-haiku-4-5-20251001 | ✅ OK |
| opus[1m] | plan | claude-opus-4-8 | claude-haiku-4-5-20251001 | claude-haiku-4-5-20251001 | ✅ OK |
| opus[1m] | acceptEdits | claude-opus-4-8 | claude-haiku-4-5-20251001 | claude-haiku-4-5-20251001 | ✅ OK |
| opus[1m] | bypassPermissions | claude-opus-4-8 | claude-haiku-4-5-20251001 | claude-haiku-4-5-20251001 | ✅ OK |
| sonnet | default | claude-sonnet-4-6 | claude-haiku-4-5-20251001 | claude-haiku-4-5-20251001 | ✅ OK |
| sonnet | plan | claude-sonnet-4-6 | claude-haiku-4-5-20251001 | claude-haiku-4-5-20251001 | ✅ OK |
| sonnet | acceptEdits | claude-sonnet-4-6 | claude-haiku-4-5-20251001 | claude-haiku-4-5-20251001 | ✅ OK |
| sonnet | bypassPermissions | claude-sonnet-4-6 | claude-haiku-4-5-20251001 | claude-haiku-4-5-20251001 | ✅ OK |
| sonnet[1m] | default | claude-sonnet-4-6 | claude-haiku-4-5-20251001 | claude-haiku-4-5-20251001 | ✅ OK |
| sonnet[1m] | plan | claude-sonnet-4-6 | claude-haiku-4-5-20251001 | claude-haiku-4-5-20251001 | ✅ OK |
| sonnet[1m] | acceptEdits | claude-sonnet-4-6 | claude-haiku-4-5-20251001 | claude-haiku-4-5-20251001 | ✅ OK |
| sonnet[1m] | bypassPermissions | claude-sonnet-4-6 | claude-haiku-4-5-20251001 | claude-haiku-4-5-20251001 | ✅ OK |
| haiku | default | claude-haiku-4-5-20251001 | claude-haiku-4-5-20251001 | claude-haiku-4-5-20251001 | ✅ OK |
| haiku | plan | claude-sonnet-4-6 | claude-sonnet-4-6 | claude-haiku-4-5-20251001 | ❌ BUG |
| haiku | acceptEdits | claude-haiku-4-5-20251001 | claude-haiku-4-5-20251001 | claude-haiku-4-5-20251001 | ✅ OK |
| haiku | bypassPermissions | claude-haiku-4-5-20251001 | claude-haiku-4-5-20251001 | claude-haiku-4-5-20251001 | ✅ OK |
| opusplan | default | claude-sonnet-4-6 | claude-haiku-4-5-20251001 | claude-haiku-4-5-20251001 | ✅ OK |
| opusplan | plan | claude-opus-4-8 | claude-opus-4-8 | claude-haiku-4-5-20251001 | ❌ BUG |
| opusplan | acceptEdits | claude-sonnet-4-6 | claude-haiku-4-5-20251001 | claude-haiku-4-5-20251001 | ✅ OK |
| opusplan | bypassPermissions | claude-sonnet-4-6 | claude-haiku-4-5-20251001 | claude-haiku-4-5-20251001 | ✅ OK |
| opusplan[1m] | default | claude-sonnet-4-6 | claude-haiku-4-5-20251001 | claude-haiku-4-5-20251001 | ✅ OK |
| opusplan[1m] | plan | claude-opus-4-8 | claude-opus-4-8 ¹ | claude-haiku-4-5-20251001 | ❌ BUG |
| opusplan[1m] | acceptEdits | claude-sonnet-4-6 | claude-haiku-4-5-20251001 | claude-haiku-4-5-20251001 | ✅ OK |
| opusplan[1m] | bypassPermissions | claude-sonnet-4-6 | claude-haiku-4-5-20251001 | claude-haiku-4-5-20251001 | ✅ OK |
| default | default | claude-opus-4-8 | claude-haiku-4-5-20251001 | claude-haiku-4-5-20251001 | ✅ OK |
| default | plan | claude-opus-4-8 | claude-haiku-4-5-20251001 | claude-haiku-4-5-20251001 | ✅ OK |
| default | acceptEdits | claude-opus-4-8 | claude-haiku-4-5-20251001 | claude-haiku-4-5-20251001 | ✅ OK |
| default | bypassPermissions | claude-opus-4-8 | claude-haiku-4-5-20251001 | claude-haiku-4-5-20251001 | ✅ OK |
¹ Secondary symptom for
opusplan[1m] × plan: the[1m]extended-thinking flag is silently dropped on the subagent — parent runsclaude-opus-4-8with extended thinking, Explore subagent gets plainclaude-opus-4-8.Key observations
permissionMode=planonly — no regressions outside the predicted set.opus[1m] × planis not affected — the bug is gated on the plan-upgrade branch insideopusplan/haikualiases, not plain aliases with[1m].opusplan × defaultcorrectly resolves toclaude-sonnet-4-6(lighter model, no bug). The upgrade only misfires in plan mode.default × planis unaffected — no plan-upgrade branch in thedefaultalias.opusplan/opusplan[1m] × plan→ ~15× cost overrun per Explore subagent call;haiku × plan→ ~3×.Root cause (unchanged from original report):
mRreads the session-global alias at subagent query-setup and fires the plan-mode upgrade branch unconditionally, discarding the correctly-resolved per-subagentmainLoopModel. Plain aliases hitreturn q→ unaffected.Thanks for fixing the underlying inconsistency in 2.1.198. Closing the loop with a full re-test below.
Re-tested on CC 2.1.198 (2026-07-02) — 32 of 32 cells consistent, 0 BUG cells.
The 2.1.198 changelog entry:
removes the haiku-default premise this bug depended on. Since Explore no longer has a
"default to haiku unless overridden" behavior at all — it now always inherits the
parent's resolved model (capped at opus) — the three previously-broken cells
(
opusplan+plan,opusplan[1m]+plan,haiku+plan) are no longer special-cased:they just inherit like every other cell.
| alias | mode | parent model | Explore model | status |
|---|---|---|---|---|
| opus | default | claude-opus-4-8 | claude-opus-4-8 | ✅ ok |
| opus | plan | claude-opus-4-8 | claude-opus-4-8 | ✅ ok |
| opus | acceptEdits | claude-opus-4-8 | claude-opus-4-8 | ✅ ok |
| opus | bypassPermissions | claude-opus-4-8 | claude-opus-4-8 | ✅ ok |
| opus[1m] | default | claude-opus-4-8 | claude-opus-4-8 | ✅ ok (explicit id, alias broken*) |
| opus[1m] | plan | claude-opus-4-8 | claude-opus-4-8 | ✅ ok (explicit id, alias broken*) |
| opus[1m] | acceptEdits | claude-opus-4-8 | claude-opus-4-8 | ✅ ok (explicit id, alias broken*) |
| opus[1m] | bypassPermissions | claude-opus-4-8 | claude-opus-4-8 | ✅ ok (explicit id, alias broken*) |
| sonnet | default | claude-sonnet-5 | claude-sonnet-5 | ✅ ok |
| sonnet | plan | claude-sonnet-5 | claude-sonnet-5 | ✅ ok |
| sonnet | acceptEdits | claude-sonnet-5 | claude-sonnet-5 | ✅ ok |
| sonnet | bypassPermissions | claude-sonnet-5 | claude-sonnet-5 | ✅ ok |
| sonnet[1m] | default | claude-sonnet-5 | claude-sonnet-5 | ✅ ok (explicit id, alias broken*) |
| sonnet[1m] | plan | claude-sonnet-5 | claude-sonnet-5 | ✅ ok (explicit id, alias broken*) |
| sonnet[1m] | acceptEdits | claude-sonnet-5 | claude-sonnet-5 | ✅ ok (explicit id, alias broken*) |
| sonnet[1m] | bypassPermissions | claude-sonnet-5 | claude-sonnet-5 | ✅ ok (explicit id, alias broken*) |
| haiku | default | claude-haiku-4-5-20251001 | claude-haiku-4-5-20251001 | ✅ ok |
| haiku | plan | claude-sonnet-5 | claude-sonnet-5 | ✅ ok |
| haiku | acceptEdits | claude-haiku-4-5-20251001 | claude-haiku-4-5-20251001 | ✅ ok |
| haiku | bypassPermissions | claude-haiku-4-5-20251001 | claude-haiku-4-5-20251001 | ✅ ok |
| opusplan | default | claude-sonnet-5 | claude-sonnet-5 | ✅ ok |
| opusplan | plan | claude-opus-4-8 | claude-opus-4-8 | ✅ ok |
| opusplan | acceptEdits | claude-sonnet-5 | claude-sonnet-5 | ✅ ok |
| opusplan | bypassPermissions | claude-sonnet-5 | claude-sonnet-5 | ✅ ok |
| opusplan[1m] | default | claude-opus-4-8 | claude-opus-4-8 | ⚠️ ok,
opus[1m]proxy only† || opusplan[1m] | plan | claude-opus-4-8 | claude-opus-4-8 | ✅ ok (real alias) |
| opusplan[1m] | acceptEdits | claude-opus-4-8 | claude-opus-4-8 | ⚠️ ok,
opus[1m]proxy only† || opusplan[1m] | bypassPermissions | claude-opus-4-8 | claude-opus-4-8 | ⚠️ ok,
opus[1m]proxy only† || default | default | claude-opus-4-8 | claude-opus-4-8 | ✅ ok |
| default | plan | claude-opus-4-8 | claude-opus-4-8 | ✅ ok |
| default | acceptEdits | claude-opus-4-8 | claude-opus-4-8 | ✅ ok |
| default | bypassPermissions | claude-opus-4-8 | claude-opus-4-8 | ✅ ok |
\* Separate finding, unrelated to this bug:
--model 'opus[1m]'/'sonnet[1m]'currently fail outright with "Advisor disabled — base model isn't in the advisor capability table," then "may not exist or you may not have access." The explicit full model ID with the same suffix (claude-opus-4-8[1m],claude-sonnet-5[1m]) works fine, so these 8 rows were re-tested with the explicit ID as a substitute for the alias.†
opusplan[1m]outside plan mode hits the same broken alias lookup and has no 1:1 explicit-ID substitute (it's a routing alias, not a concrete model), so these 3 rows useclaude-opus-4-8[1m]as a stand-in to confirm Explore inherits correctly for 1M-context sessions in general — they don't confirm whatopusplan[1m]itself resolves to outside plan mode. The oneopusplan[1m]cell this bug is actually about (+plan) was tested via the real alias and is solid.Closing this out on my end. Two side notes for future readers:
opus/opusplansessions will now pay parent-model rates for it;CLAUDE_CODE_SUBAGENT_MODEL=haikustill forces it back down if that's wanted, confirmed with a spot-check on this build.<alias>[1m]shorthand (opus[1m],sonnet[1m]) is currently broken on 2.1.198 as described above. Happy to file that separately if useful — flagging here since it's what forced the explicit-ID substitution in the table.This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.