Skip to content

Agents panel shows the parent session's model/effort for every subagent instead of the subagent's own #7281

Description

@lnieuwenhuis

Summary

Every row in the Agents panel displays the parent session's model and effort rather than what the subagent actually ran on. With a session on opus-5 · xhigh, eight subagents spanning four different models all rendered as opus-5 · xhigh.

The token counts and titles on those rows are correct — only model/effort are wrong — so it reads as authoritative rather than as a placeholder. That's what makes it costly: it silently misreports model routing, and anyone reasoning from the panel will draw wrong conclusions about which model handled what.

Reproduction

Requires custom subagents that declare model:/effort: in frontmatter (~/.claude/agents/*.md).

  1. Run a session on a model/effort distinct from your agents' — here opus-5 at xhigh.
  2. Spawn agent types whose definitions pin different models, e.g. model: haiku / effort: low, model: sonnet / effort: medium, model: fable / effort: high.
  3. Open the Agents panel.

Expected — each row shows that agent's own model/effort.
Actual — every row shows opus-5 · xhigh.

Evidence

Eight probes, each Reply with the single word ACK. "Panel" is what the sidebar rendered; "actual" is from the SDK's own run records at ~/.claude/projects/<project>/<session>/subagents/agent-*.jsonl, where message.model on each assistant record is the API's report of which model served the request:

agent type agent definition panel showed actually ran
lookup-fast haiku / low opus-5 · xhigh claude-haiku-4-5-20251001, no effort
implement-fast opus / low opus-5 · xhigh claude-opus-5 / low
general sonnet / medium opus-5 · xhigh claude-sonnet-5 / medium
review-standard sonnet / high opus-5 · xhigh claude-sonnet-5 / high
deep-reasoning opus / high opus-5 · xhigh claude-opus-5 / high
critical-review opus / xhigh opus-5 · xhigh claude-opus-5 / xhigh ✅
long-horizon fable / high opus-5 · xhigh claude-fable-5 / high
inherit (inherits parent) opus-5 · xhigh claude-opus-5 / xhigh ✅

The two correct rows are the two that coincidentally match the session — critical-review happens to share it, and inherit is supposed to.

Same eight agents in both columns: panel token counts match the records exactly (8.8k↔8895, 16.7k↔16684, 22.2k↔22151, 12.0k↔11960, 16.5k↔16537, 7.7k↔7707, 16.7k↔16729, 16.4k↔16421). Two independent signals confirm different models really ran: the haiku probe emitted out=73 tokens where every Opus/Sonnet probe emitted out=5, and it carries no effort field at all — Haiku 4.5 doesn't take reasoning effort, yet the panel labels it xhigh.

Cause

apps/server/src/provider/Layers/ClaudeAdapter.ts:3194-3202 seeds both fields at launch:

const model =
  trimmedString(launchInput?.model) ?? trimmedString(context.session.model ?? undefined);
const rawLaunchEffort = launchInput?.effort;
const effort =
  trimmedString(rawLaunchEffort) ??
  (typeof rawLaunchEffort === "number" && Number.isFinite(rawLaunchEffort)
    ? String(rawLaunchEffort)
    : context.currentEffort);

The comment above it — "absent ones inherit the session's selection (SDK behavior)" — holds only for agent types that genuinely inherit (general-purpose, claude, inherit). For a file-defined agent, the SDK resolves model and effort from the agent's own frontmatter and pushes an effort layer for that subagent; the Agent tool input carries no override, so this fallback attributes the session's values to an agent that never used them. The adapter can't see the agent definition, so the launch-time seed is unavoidably a guess — the issue is that the guess is presented as fact.

Effort is never corrected. The model has a refinement path at ClaudeAdapter.ts:2876-2883, which overwrites owningAgent.model from the authoritative message.model on subagent assistant snapshots, and taskLinkageFor (:1034-1035) propagates both onward. There is no equivalent for effort anywhere — context.currentEffort is the only source it ever has.

For model, the refinement path exists but did not land for these direct spawns (all eight kept the seeded value). I haven't diagnosed why; the agentIdForParentToolUse(…, assistantParentToolUseId) lookup at :2878 seems the place to look, and these probes were short-lived (1–2s), so a snapshot ordering or matching issue is plausible. Flagging it as observed rather than asserting the mechanism.

Suggested direction

  • Give effort the same snapshot-refinement treatment model has, or read both from the subagent run records, which are authoritative.
  • Render unknown-until-refined as absent rather than falling back to session values — a blank is honest, a wrong model is not.
  • Don't synthesize an effort for models that don't support one: the lookup-fast record has no effort field, and xhigh on Haiku 4.5 is not a value that can exist.
  • Worth a regression test that a subagent whose definition pins a model differing from the session renders the pinned one.

Environment

t3code on macOS 26 (Darwin 27.0.0) · Claude Code 2.1.233 · session claude-opus-5 at xhigh

Activity

  1. mckhendry commented on Sep 4, 2026

    @mckhendry

    On 0.0.38 I hit a second trigger for the same false value: a background subagent pinned to model: opus and served throughout by claude-opus-5 rendered as fable-5-1 · high — the parent session's model — after I resumed it with SendMessage following an API error, and an unresumed failure rendered <synthetic> · high.

    The launch-time seed in ClaudeAdapter.ts falls back to the session model when the launching tool input carries no model, as you describe. A SendMessage resume re-emits task_started with no model and a new tool_use_id, so the record is overwritten with the parent's value and rebound to an id that agentIdForParentToolUse never matches on later assistant snapshots. <synthetic>, the model field on Claude Code's synthetic error message, is accepted like any other model.

    04:55:29 task.started model=opus
    04:59:25 task.updated model=<synthetic> status=failed
    13:15:48 task.started model=claude-fable-5-1   (resume)
    13:19:37 task.updated model=claude-fable-5-1 status=completed
    

    That rebinding is one confirmed instance of the matching failure you suspected: refinement cannot land because the lookup has nothing to match. Workaround is to read message.model from the subagent jsonl.

    Drafted with Claude Code (Claude Opus 5) from source and transcripts on my machine; I reviewed and posted it.

  2. SamGu-NRX commented on Sep 15, 2026

    @SamGu-NRX

    Still observing incorrect parent-model attribution on macOS with installed nightly 0.0.41-nightly.20260913.1646, including completed stages displayed after resuming a Workflow with resumeFromRunId.

    The parent runs gpt-6-astra(medium). Five build, deployment and repair stages explicitly select an Opus agent. Their original child JSONL transcripts all record message.model: "claude-opus-5", with actual tool calls and completed results. Yet earlier completed stages in the resumed Workflow display gpt-6-astra(medium) and no token count. The newly executed stage displays Opus with its tokens and tools. The same repair stage appears as Opus in one run and Astra in a later resumed view.

    This is a display attribution problem in these samples, not evidence that the work ran on Astra or was skipped after a quota error. A missing token count alone cannot establish either.

    This appears to be another instance of this issue's symptom. I have not tested whether #7287 covers the resumed-Workflow case. Reused stages should retain their original verified model, or display unknown when identity cannot be recovered, rather than present the parent's model as fact.

    Investigated with Codex using local child transcripts and Workflow journals.

  3. etherious1804 commented on Sep 23, 2026

    @etherious1804

    Also seeing this on 0.0.40 (macOS). Custom agents in ~/.claude/agents/ with effort: low and effort: medium in frontmatter all show the parent session's high in the Agents panel, while each subagent's own transcript JSONL records "effort":"low" / "effort":"medium" on every request. So the agents run at the right effort and only the label is wrong. That makes the panel misleading for anyone using per-agent effort to control cost. Happy to re-test once V2 lands.

  4. DiegoJohnsonL commented on Sep 24, 2026

    @DiegoJohnsonL

    Closed #13333 because of the V2 freeze. Note for V2: the SDK only reports a subagent's real effort on hooks fired inside it. PreToolUse and SubagentStop inputs carry agent_id (equal to task_id) and effort.level; SubagentStart has no effort (SDK 0.3.276).

  5. PedroNavHurDCA commented on Sep 29, 2026

    @PedroNavHurDCA

    I'm still seeing this on 0.0.43-nightly.20260929.2416 on Linux. My custom agent in ~/.claude/agents/ sets model: claude-sonnet-5-5 and effort: low. Its card shows whatever effort the thread is on, so medium in one thread and high in another. The agent's transcript records "effort":"low","perTurnEffort":"low" on every turn, so it really runs at low.

    The model label is right for me. handleAssistantMessage swaps in the subagent's model as soon as its first message arrives. That leaves effort as the only field stuck on the start-time fallback.

    #14272 would leave effort blank, which beats showing a wrong value. There's another option, though. task_started already carries subagent_type, and a file-defined agent keeps its effort in frontmatter, in ~/.claude/agents/<type>.md, the project's .claude/agents/, or a plugin's agents folder. If the adapter read that file at start, custom agents would show their real effort. Agents without an effort: line, general-purpose included, would keep following the session or show nothing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions