Subagent failure notices don't include the error reason — parent sees "Its closing message:" followed by nothing #4334
Replies: 1 comment
|
Verified all of this against rc.2 (HEAD b150a55). The trace is exact, and there is one thing you did not say that makes the fix even smaller: the error detail is already in scope at both call sites — it is discarded, not unavailable.
Two sites, both of which have the failure in hand:
So the fix is not "go read the child session file" — it is "stop dropping the value you are already holding." That is the strongest argument for why this should be fixed in notifySettlement rather than in the child session record.
The notice is intentionally model-facing and brief (the doc comment at continuation.ts:290-296 frames it as "one line ... in the parent's own task vocabulary"), so keeping the error to one summary line with an optional detail line respects that contract — the parent should get the cause without the notice turning into a log dump. stopReason stays the discriminator; the error text is context for it, not a new state. This is a genuinely small, high-value fix — worth doing even before the issues/PRs channel reopens, since it is exactly the kind of observability gap that makes parent agents misdiagnose child failures as truncation. |
Uh oh!
There was an error while loading. Please reload this page.
Summary
When a background subagent fails mid-turn (e.g. a model API 429 rate-limit), the settlement notice injected into the parent conversation reads:
and then nothing. The actual failure reason never reaches the parent agent, so the failure looks "truncated" and cannot be self-diagnosed without manually digging through
~/.dsh/sessions/.../session.jsonl.zstd.Version:
@deepseek-ai/dsh-subagent@0.1.1-rc.2(dsh CLI0.1.1-rc.2).Root cause (traced in code)
In
packages/subagent/subagent/lib/index.js:settlementSummary(childId, stopReason)mapsstopReasonto a fixed one-liner ("error"→"failed before it finished."). No error detail is forwarded.notifySettlement()builds the notice as[summary text] + ["Its closing message:"] + terminal.output, whereterminal.outputis the last partial assistant output of the child. For a child that died mid-turn, that output is onlyreasoning+tool-callblocks (e.g. an in-flightbash/readcall) — no final text message.429 RATE_LIMIT: Rate limit exceeded for api_key ...— is recorded in the child sessionturn/endrecord (reason.kind: "error", with the full API message), butnotifySettlement()never reads it.Related gap:
observeRun()rejection path emitssubagent/endwith only{ stopReason: "error" }, droppinglastAssistantMessageas well — so a plugin listening to the publicsubagent/endevent also cannot see the details without reading the child session record itself.Suggested improvement
notifySettlement(), append a one-line error summary to the notice when the terminal carries error details (e.g.Failed with RATE_LIMIT: 429: <message>), so the parent agent can see why the child died.lastAssistantMessage) in thesubagent/endevent payload rejection path, so community plugins can build diagnostics on top of it.Impact
The fix itself is small (a few lines in
notifySettlement()/settlementSummary()); happy to submit a PR whenever the project accepts external contributions.All reactions