Skip to content

[Bug]: Claude subagents show the thread's effort instead of the one their agent definition sets #24143

Closed as duplicate

Description

@smolpaw

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area

apps/server

Steps to reproduce

An agent with shell access can run all of this.

  1. In a project, add an agent definition that sets its own effort:
    mkdir -p .claude/agents && cat > .claude/agents/effort-probe.md <<'EOF'
    ---
    name: effort-probe
    description: Replies OK.
    effort: low
    ---
    Reply with exactly the word OK.
    EOF
  2. Start a new Claude thread in that project at xhigh effort. Claude Code loads agent definitions when the session starts.
  3. Send: Call the Agent tool with subagent_type "effort-probe" and the prompt "Reply OK". Do not pass effort.
  4. Read the effort T3 stored for the subagent. ~/.t3/userdata is the state directory of an installed build:
    sqlite3 -readonly ~/.t3/userdata/statev2.sqlite "select json_extract(payload_json,'$.title'), json_extract(payload_json,'$.modelSelection') from orchestration_v2_projection_subagents where origin = 'provider_native' order by started_at desc limit 1"
  5. Read the effort the subagent ran at, from its Claude Code transcript:
    jq -r 'select(.type=="assistant") | .effort' "$(ls -t ~/.claude/projects/*/*/subagents/agent-*.jsonl | head -1)" | sort | uniq -c

Expected behavior

The subagent shows low, the effort it runs at, or no effort when T3 can't tell.

Actual behavior

T3 stores and shows xhigh, the thread's effort. Claude Code ran the subagent at low:

step 4: Effort probe reply OK|{"instanceId":"claudeAgent","model":"claude-opus-5-5","options":[{"id":"effort","value":"xhigh"}]}
step 5: 2 low

updateClaudeSubagentNode in apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts (around line 5669 on main @ 66ee628) takes the effort from the Agent call's effort argument, else from the owner subagent, else from the thread. Claude Code applies a definition's effort after the call is written, so T3 never sees it.

A PreToolUse hook that sets effort through updatedInput hits the same path. With a hook that sets high, every request goes out at high and T3 shows xhigh.

Related: #14842 reported this on 0.0.40 and was closed when V2 stopped showing effort. #17496 brought the label back with the thread fallback. Open PR #17042 removes the fallback, but its "Not verified" list includes a live custom-agent run. The steps above are that run.

Impact

Minor bug or occasional failure

Version or commit

0.1.2. The code is unchanged on main @ 66ee628.

Environment

T3 Code 0.1.2 on Arch Linux x86_64 (desktop) and Ubuntu 24.04 arm64 (service). Claude Code 2.1.309, Claude Opus 5.5.

Workaround

Read the effort from the subagent's transcript (step 5).

Activity

  1. izzat316 commented on Oct 23, 2026

    @izzat316

    Confirmed on macOS: T3 Code Nightly 0.1.2-nightly.20261022.3526 (desktop), macOS 26.6.2, Claude Code 2.1.309. The definition case reproduces as described with a user-level definition in ~/.claude/agents/ (Haiku, effort: medium): from a max thread, T3 stores max and the subagent runs at medium. On earlier nightlies with the same fallback (Claude Code 2.1.306–2.1.308), it also went the other way: with the thread at medium, a definition with effort: high showed medium and ran at high.

    A third path: Claude Code's maxEffortLevel clamp. Claude Code can also lower the effort after the Agent call is written, and T3 doesn't see that either. This path also applies when the Agent call passes effort explicitly, and #17042 keeps explicit values, so that case would still show the wrong effort after it merges.

    Steps, using the same checks as steps 4 and 5 above:

    1. In ~/.claude/settings.json, cap Sonnet at high (these are my exact settings):
      "modelSettings": { "claude-sonnet-5-5": { "effortLevel": "high", "maxEffortLevel": "high" } }
    2. Start a Claude Opus 5.5 thread at max.
    3. Send: Call the Agent tool three times with subagent_type "general-purpose" and the prompt "Reply OK": model "sonnet" without passing effort, model "sonnet" with effort "xhigh", and model "haiku" without passing effort.
    4. Run steps 4 and 5 above for each subagent.
    Agent call T3 stores Ran at (transcript .effort)
    model: "sonnet", effort not passed max high
    model: "sonnet", effort: "xhigh" xhigh high
    model: "haiku", effort not passed (control, no cap) max max

    $CLAUDE_EFFORT inside each subagent matched its transcript (high, high, max). The effortLevel default isn't what lowers it: from a medium thread, the same Sonnet subagent ran at medium (earlier nightly, Claude Code 2.1.306).

    The first row is the common one in practice. In my state database, 34 Sonnet general-purpose subagents launched without an effort argument from a max thread over Oct 20–23 show max and ran at high on every turn. #17042 would blank that label; the second row would keep showing xhigh. This is the subagent side of #17110, where T3 doesn't read the maxEffortLevel cap. #17496 notes that Claude Code only publishes the applied effort on Remote Control init frames and in hook inputs, not on the stream T3 reads.


    Investigated by Claude Opus 5.5 in T3 Code.

  2. kolodziejm commented on Oct 30, 2026

    @kolodziejm

    Still happens on main @ 6575ce3, with a user-level agent definition and a model override.

    • ~/.claude/agents/explorer.md sets model: sonnet and effort: medium.
    • The parent thread runs Opus at high and calls Agent with subagent_type: "explorer" and no effort argument.
    • The subagent's transcript has claude-sonnet-5-5 with effort: "medium" on every request (perTurnEffort: "medium" too).
    • T3 shows Claude Sonnet 5.5 · high in the task row and the Lineage hover card.

    The model is right and the effort is wrong. The fallback is now at:

    const effort =
    existingSubagent === undefined
    ? (input.effort ??
    input.owner?.effort ??
    compileClaudeModelSelection(input.context.input.modelSelection).effort)
    : existingSubagent.effort;

    The model starts from the same launch data. It ends up right only because it is later corrected from the subagent's own assistant messages (L8616–L8651). Effort gets no such correction.

    Claude Code 2.1.312.

  3. smolpaw commented on Nov 5, 2026

    @smolpaw
    Author

    Still happens on T3 Code 0.1.7 with Claude Code 2.1.318. Claude Code's new CLAUDE_CODE_SUBAGENT_EFFORT (added in 2.1.316) makes it hit every subagent launched without an effort argument, not only agents with an effort: definition.

    CLAUDE_CODE_SUBAGENT_EFFORT sets the effort a subagent runs at when neither the Agent call's effort nor the agent definition's effort sets one. Without it, a subagent inherits the session's effort. Claude Code applies it when it starts the subagent, after the Agent call is written, just as it applies a definition's effort. So T3 sees no effort on the call and falls back to the thread's.

    This replaces the PreToolUse hook from the report. That hook did the same thing through updatedInput and ran into the same path.

    Steps:

    1. In ~/.claude/settings.json, set "env": { "CLAUDE_CODE_SUBAGENT_EFFORT": "high" }.
    2. Start a Claude Opus 5.5 thread at xhigh.
    3. Send: Call the Agent tool twice with subagent_type "general-purpose" and the prompt "Reply OK": once without passing effort, once with effort "medium".
    4. Run steps 4 and 5 from the report for each subagent.
    Agent call T3 stores Ran at (transcript .effort)
    effort not passed xhigh high
    effort: "medium" (control) medium medium

    The parent ran at xhigh on every turn. With the parent thread at high, the same subagent shows high and runs at high, so this only shows up when the thread and the env var differ.

    The fallback is unchanged on main @ 67eebac:

    const effort =
    existingSubagent === undefined
    ? (input.effort ??
    input.owner?.effort ??
    compileClaudeModelSelection(input.context.input.modelSelection).effort)
    : existingSubagent.effort;

    T3 can't take the effort from its own launch settings: Claude Code reads the env var from settings.json, and the agent definition can override it. Dropping the thread fallback, as #17042 does, would show no effort instead of a wrong one for these subagents too. Showing the real level would need the effort Claude Code applied. Claude Code writes it into the subagent's transcript (.effort and perTurnEffort on each assistant entry), but per #17496 not on the stream T3 reads.


    Investigated by Claude Opus 5.5 in T3 Code.

  4. Collexi-Dev commented on Nov 10, 2026

    @Collexi-Dev

    Still happens on T3 Code 0.1.8-nightly.20261109.4399 (desktop app on macOS connected to a Linux server, Claude Code 2.1.322).

    • User-level definition ~/.claude/agents/opus-medium.md: model: opus, effort: medium.
    • Parent thread: Claude Fable 5.2 at Extra High, calling Agent with subagent_type: "opus-medium" and no effort.
    • Every subagent transcript (subagents/agent-<id>.jsonl) has claude-opus-5-5 with "effort":"medium" on all requests. The .meta.json files have "agentType":"opus-medium".
    • T3 stores modelSelection: { model: "claude-opus-5-5", options: [{ id: "effort", value: "xhigh" }] } and the Lineage hover card shows Opus 5.5 · Claude · xhigh.

    The wrong effort also passes to nested forks. Two of the opus-medium subagents started subagent_type: "fork" children. All six forks ran at medium according to their transcripts, but T3 stored xhigh for them too. They get it through input.owner?.effort, so the fallback's wrong value is passed down a level.

    Counts from one thread: 27 Claude subagents, all of which ran at medium. T3 stored xhigh for 23 of them (17 opus-medium and 6 nested forks) and no effort for the other 4.

    Same fallback at ClaudeAdapterV2.ts:6867 on the v0.1.8-nightly.20261109.4399 tag, unchanged on main.

    Comment drafted by Claude Opus 5.5 in Claude Code, from a T3 Code triage session.

  5. DaavyC commented on Nov 10, 2026

    @DaavyC

    Still happens on T3 Code 0.1.9-nightly.20261110.4478 (desktop app on Windows 11, Claude Code 2.1.322).

    • User-level definition ~/.claude/agents/explorer.md: model: haiku, effort: medium.
    • Parent threads: Claude Fable 5.2 and Claude Opus 5.5, calling Agent with subagent_type: "explorer" and no effort.
    • 29 explorer subagents from one day, across 7 threads. Every subagent transcript (subagents/agent-<id>.jsonl) has claude-haiku-5-5 with "effort":"medium" on all requests.
    • T3 stored the parent thread's effort in modelSelection for each of them:
    Parent effort T3 stored Transcript Subagents
    xhigh xhigh medium 19
    low low medium 6
    high high medium 1
    medium medium medium 3

    The label is wrong in both directions. It shows a higher effort than the subagent used when the thread is at xhigh, and a lower one when the thread is at low.

    Reported by an AI agent: Claude Fable 5.2 at xhigh effort, running in T3 Code, posted at the account owner's request.

  6. syz51 commented on Nov 11, 2026

    @syz51

    A working fix for the "show the real level" part, in case it helps: syz51#12 (a fork of 0.1.9).

    It keeps the current guess as the first label, then replaces it with the effort from the subagent's transcript (<config>/projects/<project>/<session>/subagents/agent-<task_id>.jsonl). The adapter reads it on the subagent's first assistant frame and again when the task settles. The read runs outside the frame pump, with at most 8 tries per subagent. The child thread's model selection gets the same correction.

    Because it reads what Claude Code applied, it should cover the definition, CLAUDE_CODE_SUBAGENT_EFFORT, hook and maxEffortLevel paths alike. Checked with an adapter test for both moments (label goes medium → xhigh). I have not yet run it against a live custom agent.

    This could sit on top of #17042: no label until the transcript gives one.

  7. juliusmarminge commented on Nov 12, 2026

    @juliusmarminge
    Member

    This is the same problem as #17042, so I'm closing it in favour of that one. If it is different, say how and it will be reopened.

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