Compare commits

..

1 Commits

Author SHA1 Message Date
Ben Brandt
198a189e27 fix: list a subagent's background Bash commands
Claude Code sends no tool_use_result with a subagent's tool results, so a
subagent's background Bash command was never recognized: its terminal was
reported exited at once and it was missing from the subagent's background
list. The task_started that names the Bash call comes before the result, so
use it when the result has no structured backgroundTaskId.
2026-11-06 17:36:50 +01:00
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]);