Skip to content

[Bug]: Agent / sessions_spawn tool_use can emit with no matching tool_result and no parent-visible signal — silent subagent drop (~3.8% historical rate) #88856

Description

@BryceMurray

Environment

  • OpenClaw: 2026.5.22
  • Agent harness: claude-cli (direct Anthropic), model claude-opus-4-7
  • Host: single-VPS Linux deployment, no container
  • Transcript scope: workspace project under ~/.claude/projects/<slug>/

TL;DR

Across the local historical transcript corpus, 23 of 602 (3.82%) Agent / mcp__openclaw__sessions_spawn tool_use blocks emitted by the assistant have no matching tool_result block anywhere later in the same transcript. The subagent either died, was killed, or the runtime dropped the result before the parent resumed — but no signal of that failure reaches the parent's context. The parent transcript shows the spawn tool_use and nothing after it, leaving the model to either narrate forward as if the spawn completed (worst case), wait indefinitely, or fabricate the result.

The ask is for first-class harness signal on a dropped / killed / disconnected subagent: e.g. an injected tool_result with is_error=true and an error_type field, or a harness-side notification surfaced into the parent context that the model can read on the next turn.

Repro shape

This is an observed-rate report against an existing on-disk transcript corpus, not a synthetic single-shot repro. The detection is mechanical:

spawn_ids   = jq '.type=="assistant" → .message.content[] → select(.type=="tool_use" AND (.name=="Agent" OR .name=="mcp__openclaw__sessions_spawn")) → .id'
result_ids  = jq '.type=="user" → .message.content[] → select(.type=="tool_result") → .tool_use_id'
orphans     = spawn_ids − result_ids

A spawn id that appears in spawn_ids and never in result_ids is an orphan. Backtest result on this host's transcript corpus:

Metric Value
Transcripts scanned 13,505
Total spawn tool_uses 602
Orphans 23
Orphan rate 3.82%

Why this matters

Three downstream behaviors the parent can take when the result never arrives, all of them bad:

  1. Silent narrate-forward. The model, with no visible failure signal, continues as if the subagent returned cleanly. Whatever the parent was going to do with the result (summarize, decide, act) gets executed against an empty / fabricated stand-in.
  2. Indefinite stall. The model waits for the result, with no way to know it isn't coming. The user sees a non-responding session.
  3. Fabricated synthesis. The model hallucinates plausible output for the missing subagent and downstream work proceeds against fiction.

All three are operator-visible only by inspecting the transcript after the fact. A first-class signal (synthetic error tool_result, or harness-emitted system reminder) would let the model see the failure on the next turn and decide whether to re-fire, refine the prompt, or explicitly drop the work.

Current local workaround

A Stop hook that diffs spawn_ids − result_ids per transcript and surfaces new orphans via exit 2 stderr as a next-turn system reminder. The model reads the reminder on the next turn and chooses re-fire / refine / drop.

# stderr message shape
ORPHANED SUBAGENT(S) DETECTED: N Agent/sessions_spawn tool_use(s) emitted with
no tool_result returned. The subagent(s) likely died, were killed, or the
runtime dropped the result before the parent resumed.

  - <tool_use_id>  <timestamp>  <description | subagent_type | name>
  (+ M older orphan(s) silenced; see <state_file>)

Decide: re-fire with same or refined prompt, or explicitly drop the work.
Do not silently narrate forward as if it completed.

State (already-surfaced ids) is kept in an append-only ledger so the same orphan doesn't re-fire on every subsequent Stop event. The hook is detection-only — it does not retry the spawn or mutate the transcript.

The hook is a workaround, not a fix:

  • It runs on every Stop, so the first signal arrives at next-turn boundary — not at the moment the subagent dropped.
  • It surfaces stderr text the model has to parse, rather than the structured tool_result shape it already knows how to consume.
  • Every downstream user has to install and maintain it independently. First-class harness signal would obviate that.

Proposed change (observed-symptom framing, not an upstream-code claim)

Two viable shapes for first-class signal:

  1. Synthetic tool_result injection. When the harness detects a subagent has died / been killed / disconnected without returning a result, inject a tool_result block into the parent transcript with tool_use_id=<the dropped spawn>, is_error=true, and a content payload describing the failure mode (subagent died, subagent killed, runtime dropped result, etc.). The model already knows how to read tool_result and will see the failure on the next turn naturally.
  2. Harness system reminder. Emit a runtime-side system reminder into the parent's next-turn context with the same information. Less structurally clean (the model has to parse text rather than a typed result) but lower-impact change to the harness.

Either form removes the silent class of failure. Form (1) is preferable because it slots into the existing tool_use/tool_result contract and is unambiguous to the model.

A complementary observation: if there's an existing failure path in the harness that already knows the subagent dropped (e.g. a logged error), the missing link is just the surface to the parent context — not a new detection layer. The 3.82% rate suggests the failure is detected somewhere; it just isn't being signalled upstream to the parent.

This is an observed-symptom proposal, not a claim about where the bug lives. I haven't traced the subagent dispatch path in the upstream source; the right fix could be in the harness's subagent-completion handler, in the runtime's tool_result writer, or in the dispatch resolver. All three are plausible homes for the signal.

Cross-refs


Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Normal backlog priority with limited blast radius.clawsweeper:needs-live-reproClawSweeper needs live local, crabbox, or manual validation to confirm this issue.clawsweeper:needs-maintainer-reviewClawSweeper marked this issue as needing maintainer review before automation.clawsweeper:needs-product-decisionClawSweeper marked this issue as needing a product or behavior decision.clawsweeper:no-new-fix-prClawSweeper does not recommend queueing a new automated fix PR for this issue.impact:message-lossChannel message delivery can be lost, duplicated, or misrouted.impact:session-stateSession, memory, transcript, context, or agent state can drift or corrupt.issue-rating: 🐚 platinum hermitGood issue quality with a plausible reproduction path needing some confirmation.staleMarked as stale due to inactivity

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions