fix: guard getPendingAttemptCompletionToolUseId against multi-tool_use messages

When the last assistant message contains attempt_completion alongside
other tool_use blocks (e.g. read_file + attempt_completion) and the
process crashes before any tool_result is saved, the method previously
returned the attempt_completion ID. This caused resumeTaskFromHistory
to bypass the rewind and treat the task as completed, while the other
tool_uses never executed.

Now returns undefined when other tool_use blocks are present alongside
attempt_completion, allowing the rewind path to handle the cleanup
correctly.

Addresses review feedback from @roomote.
This commit is contained in:
Hannes Rudolph 2026-02-06 20:14:28 -07:00
parent db8f472fff
commit 1f04bab83f
2 changed files with 12 additions and 3 deletions

View file

@ -2129,6 +2129,13 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
block.type === "tool_use" && block.name === "attempt_completion",
)
// Only treat as pending completion if there are no other unanswered
// tool_use blocks — otherwise the rewind path is more appropriate.
if (toolUse) {
const hasOtherToolUse = content.some((block) => block.type === "tool_use" && block !== toolUse)
if (hasOtherToolUse) return undefined
}
return toolUse?.id
}

View file

@ -456,13 +456,15 @@ describe("rewind-integration", () => {
expect(parsedContent.status).toBe("completed")
})
it("attempt_completion alongside another tool_use in same assistant message", () => {
it("attempt_completion alongside another tool_use in same assistant message returns undefined", () => {
const task = createTask(mockProvider)
const now = Date.now()
// Edge case: assistant message contains both a read_file tool_use and
// an attempt_completion tool_use. getPendingAttemptCompletionToolUseId
// should still detect the attempt_completion.
// should return undefined so the rewind path is used instead —
// the other tool_uses never executed, so treating it as a pending
// completion would bypass the rewind and produce misleading context.
task.apiConversationHistory = [
{
role: "user",
@ -491,6 +493,6 @@ describe("rewind-integration", () => {
] as any[]
const pendingId: string | undefined = (task as any).getPendingAttemptCompletionToolUseId()
expect(pendingId).toBe("toolu_completion_888")
expect(pendingId).toBeUndefined()
})
})