[BUG] opusplan/haiku in plan mode: subagents (e.g. Explore) inherit plan-upgrade model instead of their defined model

Resolved 💬 3 comments Opened Jun 12, 2026 by liqlvnvn Closed Jul 2, 2026

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

  1. 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."
`
Check
~/.claude/projects/<proj>/<session-id>/subagents/agent-*.jsonl -> .message.model = claude-haiku-4-5-20251001` (correct)

  1. Run (bug):

``bash
claude --model opusplan --permission-mode plan \
-p "Use the Explore agent to list files in the current directory."
`
Same check ->
.message.model = claude-opus-4-8` (wrong)

  1. Same result in interactive mode: /model opusplan + plan mode + any prompt that spawns the Explore agent.
  1. Variant: --model haiku --permission-mode plan -> Explore runs on claude-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):

  1. In the query loop, skip the Xl()-based upgrade branches when the context is a subagent (the resolved mainLoopModel already encodes the right answer), or
  2. Have mR trust its mainLoopModel argument whenever it differs from the session's resting model (i.e., it was already explicitly resolved), or
  3. 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.

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗