[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.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗