Skip to content

Commit 8083b7d

Browse files
committed
fix(EXT-665): push tool_result BEFORE delegation to prevent loss
Root cause: In NewTaskTool.execute(), pushToolResult was called AFTER delegateParentAndOpenChild(), but delegateParentAndOpenChild disposes the parent task. This meant the tool_result was lost, and flushPendingToolResultsToHistory generated a placeholder "interrupted" tool_result instead. Fix: Push the tool_result BEFORE calling delegateParentAndOpenChild. Since the child taskId is not yet known, use a generic message. The actual completion result is injected by reopenParentFromDelegation when the child completes. Also keep the defensive duplicate check in reopenParentFromDelegation for backward compatibility with any existing task histories.
1 parent 42f2b21 commit 8083b7d

File tree

2 files changed

+18
-11
lines changed

2 files changed

+18
-11
lines changed

src/core/tools/NewTaskTool.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,16 +109,23 @@ export class NewTaskTool extends BaseTool<"new_task"> {
109109
return
110110
}
111111

112+
// IMPORTANT: Push the tool_result BEFORE delegation, because delegateParentAndOpenChild
113+
// disposes the parent task. If we push after, the tool_result is lost and
114+
// flushPendingToolResultsToHistory will generate a placeholder "interrupted" tool_result,
115+
// causing duplicate tool_results when the child completes (EXT-665).
116+
//
117+
// The child taskId isn't known yet, so we use a generic message. The actual completion
118+
// result will be injected by reopenParentFromDelegation when the child completes.
119+
pushToolResult(`Delegating to subtask...`)
120+
112121
// Delegate parent and open child as sole active task
113-
const child = await (provider as any).delegateParentAndOpenChild({
122+
await (provider as any).delegateParentAndOpenChild({
114123
parentTaskId: task.taskId,
115124
message: unescapedMessage,
116125
initialTodos: todoItems,
117126
mode,
118127
})
119128

120-
// Reflect delegation in tool result (no pause/unpause, no wait)
121-
pushToolResult(`Delegated to child task ${child.taskId}`)
122129
return
123130
} catch (error) {
124131
await handleError("creating new task", error)

src/core/tools/__tests__/newTaskTool.spec.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ describe("newTaskTool", () => {
175175
)
176176

177177
// Verify side effects
178-
expect(mockPushToolResult).toHaveBeenCalledWith(expect.stringContaining("Delegated to child task"))
178+
expect(mockPushToolResult).toHaveBeenCalledWith("Delegating to subtask...")
179179
})
180180

181181
it("should not un-escape single escaped \@", async () => {
@@ -280,7 +280,7 @@ describe("newTaskTool", () => {
280280
expect(mockStartSubtask).toHaveBeenCalledWith("Test message", [], "code")
281281

282282
// Should complete successfully
283-
expect(mockPushToolResult).toHaveBeenCalledWith(expect.stringContaining("Delegated to child task"))
283+
expect(mockPushToolResult).toHaveBeenCalledWith("Delegating to subtask...")
284284
})
285285

286286
it("should work with todos parameter when provided", async () => {
@@ -311,7 +311,7 @@ describe("newTaskTool", () => {
311311
"code",
312312
)
313313

314-
expect(mockPushToolResult).toHaveBeenCalledWith(expect.stringContaining("Delegated to child task"))
314+
expect(mockPushToolResult).toHaveBeenCalledWith("Delegating to subtask...")
315315
})
316316

317317
it("should error when mode parameter is missing", async () => {
@@ -423,7 +423,7 @@ describe("newTaskTool", () => {
423423
expect(mockStartSubtask).toHaveBeenCalledWith("Test message", [], "code")
424424

425425
// Should complete successfully
426-
expect(mockPushToolResult).toHaveBeenCalledWith(expect.stringContaining("Delegated to child task"))
426+
expect(mockPushToolResult).toHaveBeenCalledWith("Delegating to subtask...")
427427
})
428428

429429
it("should REQUIRE todos when VSCode setting is enabled", async () => {
@@ -501,7 +501,7 @@ describe("newTaskTool", () => {
501501
)
502502

503503
// Should complete successfully
504-
expect(mockPushToolResult).toHaveBeenCalledWith(expect.stringContaining("Delegated to child task"))
504+
expect(mockPushToolResult).toHaveBeenCalledWith("Delegating to subtask...")
505505
})
506506

507507
it("should work with empty todos string when VSCode setting is enabled", async () => {
@@ -536,7 +536,7 @@ describe("newTaskTool", () => {
536536
expect(mockStartSubtask).toHaveBeenCalledWith("Test message", [], "code")
537537

538538
// Should complete successfully
539-
expect(mockPushToolResult).toHaveBeenCalledWith(expect.stringContaining("Delegated to child task"))
539+
expect(mockPushToolResult).toHaveBeenCalledWith("Delegating to subtask...")
540540
})
541541

542542
it("should check VSCode setting with Package.name configuration key", async () => {
@@ -671,7 +671,7 @@ describe("newTaskTool delegation flow", () => {
671671
)
672672
expect(pauseEvents.length).toBe(0)
673673

674-
// Assert: tool result reflects delegation
675-
expect(mockPushToolResult).toHaveBeenCalledWith(expect.stringContaining("Delegated to child task child-1"))
674+
// Assert: tool result reflects delegation (pushed BEFORE delegation, so no child ID yet)
675+
expect(mockPushToolResult).toHaveBeenCalledWith("Delegating to subtask...")
676676
})
677677
})

0 commit comments

Comments
 (0)