Skip to content

Commit d43cb97

Browse files
committed
fix(agents): stop raw tool output leaking into subagent completion announces
selectSubagentOutputText() was falling through to snapshot.latestRawText when no assistant-produced text was available. Raw tool output is not user-facing content and must not be delivered as completion text. Fix: return undefined instead of snapshot.latestRawText so the caller falls back to readLatestAssistantReply (post-compaction result) or synthesizes a bounded failure summary. Updated tests that expected raw tool output to be announced — they now correctly expect '(no output)' since no assistant-composed text exists. Signed-off-by: Blasius Patrick <blasius.patrick@gmail.com>
1 parent 8065f36 commit d43cb97

3 files changed

Lines changed: 10 additions & 5 deletions

File tree

docs/tools/subagents.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ requester chat when the run finishes.
9494
The completion handoff to the requester session is runtime-generated
9595
internal context (not user-authored text) and includes:
9696

97-
- `Result` — latest visible `assistant` reply text, otherwise sanitized latest tool/toolResult text. Terminal failed runs do not reuse captured reply text.
97+
- `Result` — latest visible `assistant` reply text, otherwise `(no output)`. Terminal failed runs do not reuse captured reply text.
9898
- `Status` — `completed successfully` / `failed` / `timed out` / `unknown`.
9999
- Compact runtime/token stats.
100100
- A delivery instruction telling the requester agent to rewrite in normal assistant voice (not forward raw internal metadata).
@@ -432,7 +432,7 @@ Announce context is normalized to a stable internal event block:
432432
| Session ids | Child session key/id |
433433
| Type | Announce type + task label |
434434
| Status | Derived from runtime outcome (`success`, `error`, `timeout`, or `unknown`) — **not** inferred from model text |
435-
| Result content | Latest visible assistant text, otherwise sanitized latest tool/toolResult text |
435+
| Result content | Latest visible assistant text; `(no output)` if no assistant text is available |
436436
| Follow-up | Instruction describing when to reply vs stay silent |
437437

438438
Terminal failed runs report failure status without replaying captured

src/agents/subagent-announce-output.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,12 @@ function selectSubagentOutputText(
301301
if (partialProgress) {
302302
return partialProgress;
303303
}
304-
return snapshot.latestRawText;
304+
// Do not fall through to snapshot.latestRawText — raw tool output is not
305+
// user-facing content and must not be delivered as completion text. If no
306+
// assistant-produced text is available, the caller should fall back to
307+
// readLatestAssistantReply (post-compaction result) or synthesize a bounded
308+
// failure summary.
309+
return undefined;
305310
}
306311

307312
export async function readSubagentOutput(

src/agents/subagent-announce.format.e2e.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -634,7 +634,7 @@ describe("subagent announce formatting", () => {
634634

635635
const call = agentSpy.mock.calls[0]?.[0] as { params?: { message?: string } };
636636
const msg = call?.params?.message as string;
637-
expect(msg).toContain(testCase.toolOutput);
637+
expect(msg).toContain("(no output)");
638638
},
639639
);
640640

@@ -2022,7 +2022,7 @@ describe("subagent announce formatting", () => {
20222022
expect(agentSpy).toHaveBeenCalledTimes(1);
20232023
const call = agentSpy.mock.calls[0]?.[0] as { params?: { message?: string } };
20242024
const msg = call?.params?.message as string;
2025-
expect(msg).toContain("tool output only");
2025+
expect(msg).toContain("(no output)");
20262026
});
20272027

20282028
it("ignores user text when deriving fallback completion output", async () => {

0 commit comments

Comments
 (0)