fix: list a subagent's background Bash commands (#1540)

Claude Code sends no `tool_use_result` with a subagent's tool results.
The adapter recognizes a background Bash command only from the
`backgroundTaskId` in that structured result, so it never recognized one
that a subagent started. The command's terminal was reported exited
(code 0) as soon as the result came, while the command kept running, and
the command was missing from the subagent's `state_update.background`.

The real SDK stream (CLI 2.1.318) for a subagent running `sleep 30` in
the background:

```
task_started  task_id=beure6fer task_type=local_bash tool_use_id=toolu_018M… owned_by_subagent=true is_backgrounded=true
user          parent_tool_use_id=toolu_01HY… tool_result for toolu_018M…, no tool_use_result
```

The local transcripts agree: none of the 35 subagent background Bash
results across CLI 2.1.293 to 2.1.318 has a `toolUseResult`.

When a Bash result has no structured task id, the adapter now uses the
live background `local_bash` task whose `task_started` named that Bash
call. The existing subagent test now sends the stream the CLI sends. It
fails without the fix.
This commit is contained in:
Ben Brandt
2026-11-06 19:12:42 +01:00
committed by GitHub
parent ba162346c9
commit 6597626d86
3 changed files with 27 additions and 8 deletions

View File

@@ -8502,6 +8502,18 @@ export class ClaudeAcpAgent {
content,
toolUseResult,
session.toolUseCache,
(toolUseId) => {
for (const [taskId, task] of session.liveBackgroundTasks) {
if (
task.parentToolUseId === toolUseId &&
task.taskType === "local_bash" &&
task.backgrounded
) {
return taskId;
}
}
return undefined;
},
);
if (backgroundBashTask) await asyncTasks.taskBackgrounded(backgroundBashTask);
// The command's terminal runs until its task ends, unless the

View File

@@ -863,18 +863,29 @@ function recordPublished(task: AsyncTask, fields: Record<string, unknown>): void
* Recovers a background Bash task from the structured `backgroundTaskId` of
* its tool result. The text of a tool result never gives a task id: a
* foreground command can print any text.
*
* Claude Code sends no `tool_use_result` with a subagent's tool results. For
* those, `startedTaskOf` gives the background `local_bash` task whose
* `task_started` named the Bash call, which comes before the result.
*/
export function backgroundBashTaskFromToolResult(
content: unknown,
toolUseResult: unknown,
toolUses: Record<string, { name: string; input: unknown }>,
startedTaskOf?: (toolUseId: string) => string | undefined,
): (AsyncTaskStarted & { taskId: string; toolCallId: string }) | undefined {
if (!Array.isArray(content)) return undefined;
const toolResults = content.flatMap(toolResultBlock);
const bashResults = toolResults.filter((result) => toolUses[result.toolUseId]?.name === "Bash");
if (bashResults.length === 0) return undefined;
const backgroundResults = structuredBackgroundResults(toolUseResult, toolUses);
let backgroundResults = structuredBackgroundResults(toolUseResult, toolUses);
if (backgroundResults.length === 0 && startedTaskOf) {
backgroundResults = bashResults.flatMap((result) => {
const taskId = result.isError ? undefined : startedTaskOf(result.toolUseId);
return taskId ? [{ taskId, toolUseId: result.toolUseId }] : [];
});
}
const distinctTaskIds = new Set(backgroundResults.map((result) => result.taskId));
if (distinctTaskIds.size !== 1) return undefined;
const taskId = [...distinctTaskIds][0];

View File

@@ -4510,10 +4510,13 @@ describe("ACP v2 requests that subagents make", () => {
description: "make test",
tool_use_id: "toolu_bg",
parent_task_id: "agent-1",
owned_by_subagent: true,
is_backgrounded: true,
uuid: randomUUID(),
session_id: options.sessionId,
};
// As seen live (CLI 2.1.318): a subagent's tool result has no
// `tool_use_result`, so only the `task_started` above names the task.
yield childMessage(options, {
type: "user",
message: {
@@ -4526,13 +4529,6 @@ describe("ACP v2 requests that subagents make", () => {
},
],
},
tool_use_result: {
stdout: "",
stderr: "",
interrupted: false,
isImage: false,
backgroundTaskId: "bg1",
},
});
yield taskEnd(options, "agent-1", "toolu_agent-1");
yield level(options, [bash]);