Subagent spinner shows the session's effort level, not the subagent's

Status Open
Reported on v2.1.220
Maintainer reply None cached
Activity 0 comments · opened Aug 2, 2026

Summary

When a subagent with a pinned effort: in its frontmatter is running, the terminal spinner's
with <N> effort suffix reports the main session's effort rather than the subagent's. The
subagent itself runs at the correct effort — this is a display-only defect — but it makes the
effort tier of a running agent unobservable from the UI.

The same expression also clamps the displayed effort against the session's model rather than
the subagent's, so an agent pinned to a different model can have its effort validated against the
wrong model's capabilities.

Version

2.1.220 (Claude Code)
BUILD_TIME 2026-07-24T22:17:45Z
GIT_SHA   4073f59596e272f39393db4f96abc5f4b10eff21
darwin arm64

Reproduction

  1. Set a session-wide effort that differs from the one you'll pin, e.g. in ~/.claude/settings.json:

``json
{ "model": "opus", "effortLevel": "high" }
``

  1. Create .claude/agents/effort-probe.md:

```markdown
---
name: effort-probe
description: Probe for the effort level actually in force.
model: opus
effort: xhigh
tools: Bash
---

Run echo "EFFORT=[$CLAUDE_EFFORT]" and return the output verbatim.
```

  1. Launch it (Agent tool with subagent_type: effort-probe) and watch the terminal while it runs.

Expected

The spinner line for the running subagent reads … with xhigh effort — the effort pinned in the
agent's frontmatter.

Actual

It reads … with high effort — the session's effortLevel.

Meanwhile the agent returns EFFORT=[xhigh], confirming the subagent genuinely ran at its pinned
effort and only the display is wrong.

Analysis

(Identifiers below are from the minified 2.1.220 bundle and will differ across builds; the shapes
should be stable enough to locate.)

The spinner computes its suffix as:

let j = Ve(Ce => Ce.effortValue),   // global app-state effort
    q = ait(Mi(), m ?? j)           // m = turnEffort prop, Mi() = session model getter

m is turnEffort, read from the per-agent spinner record via gra(Vra.agentId). That record
is per-agent for every field:

{ mode, overrideMessage, overrideColor, overrideShimmerColor,
  isCompacting, compactingHintText, compactingStartTime,
  turnEffort, retryStatus, thinkingStartedAt, defaultVerb }

but turnEffort has exactly one writer, and it is hardcoded to the main agent:

function N0p(e){ BYe(Si()).setTurnEffort(e) }   // Si() === main agent id

N0p is called only from main-loop query setup, alongside the main loop's own effort permission
layer:

if (ws !== undefined) {
  tf.permissionLayers = [...(tf.permissionLayers ?? []), { kind: "effort", effort: ws }]
  N0p(ws)
}

So a subagent's turnEffort is never populated, m ?? j falls through to the session-wide
effortValue, and the line renders plausibly with the wrong number instead of rendering blank.

A field stored per-agent but only ever written for the main agent looks like an oversight rather
than intentional session scoping.

Second, related scoping slip

ait(Mi(), …) uses Mi(), the session model getter. ait delegates to Goe(model, effort), which
performs capability-gated clamping:

function ait(e,t){ if(t===undefined) return ""; let r=Goe(e,t); if(r===undefined) return "";
                   return ` with ${Voe(z1e(r))} effort` }
// Goe: if (!FI(e)) return;              // model doesn't support effort at all
//      if (s==="max"  && !t8e(e)) s="high"
//      if (s==="xhigh"&& !Pye(e)) s="high"

For a subagent pinned to a different model than the session's, that clamp runs against the wrong
model. A model: sonnet / effort: medium agent has its displayed effort validated against the
session's Opus capabilities, and vice versa — an agent on an effort-incapable model would still
display an effort suffix.

Suggested fix

  1. Populate turnEffort on the spawning agent's own spinner record when a subagent's effort

permission layer is applied, rather than routing every write through Si(). The per-agent state
slice already exists; only the writer's target is wrong.

  1. Resolve the model for ait from the same agent context as the effort, so the capability clamp

and the effort value come from one source.

Why it matters

Not just cosmetic in effect. Multi-tier subagent setups pin model + effort per tier
deliberately, and the spinner is the only surface that reports effort while an agent runs. Today
CLAUDE_EFFORT inside a Bash call is the only way to confirm which tier is actually in force, which
means the displayed value quietly contradicts the real one on every dispatch whose tier differs from
the session default.

View original on GitHub ↗