From 67475410409d789d3679de66477a901f4d5a31dc Mon Sep 17 00:00:00 2001 From: zerone0x Date: Mon, 26 Jan 2026 00:42:34 +0800 Subject: [PATCH] fix(agents): skip extracting tool calls from errored assistant turns When an assistant turn has stopReason: "error", the tool calls within it were never completed (often contain partialJson). Inserting synthetic tool_results for these caused Anthropic's API to reject requests with "unexpected tool_use_id" errors, permanently breaking the session. Now we skip extracting tool calls from assistant messages with stopReason: "error", preventing the broken synthetic results. Fixes #1826 Co-Authored-By: Claude --- src/agents/session-transcript-repair.test.ts | 30 ++++++++++++++++++++ src/agents/session-transcript-repair.ts | 8 ++++++ 2 files changed, 38 insertions(+) diff --git a/src/agents/session-transcript-repair.test.ts b/src/agents/session-transcript-repair.test.ts index ccc63ec7f..591a7cd80 100644 --- a/src/agents/session-transcript-repair.test.ts +++ b/src/agents/session-transcript-repair.test.ts @@ -109,4 +109,34 @@ describe("sanitizeToolUseResultPairing", () => { expect(out.some((m) => m.role === "toolResult")).toBe(false); expect(out.map((m) => m.role)).toEqual(["user", "assistant"]); }); + + it("does not insert synthetic tool results for errored/terminated assistant turns", () => { + // When an assistant turn has stopReason: "error", the tool calls within it were + // never completed. Inserting synthetic tool_results would cause Anthropic's API + // to reject requests with "unexpected tool_use_id". + // See: https://github.com/clawdbot/clawdbot/issues/1826 + const input = [ + { role: "user", content: "schedule a reminder" }, + { + role: "assistant", + content: [ + { + type: "toolCall", + id: "toolu_terminated", + name: "cron", + arguments: { action: "add" }, + partialJson: '{"action": "add", "job": {"name": "test', + }, + ], + stopReason: "error", + errorMessage: "terminated", + }, + { role: "user", content: "what happened?" }, + ] satisfies AgentMessage[]; + + const out = sanitizeToolUseResultPairing(input); + // Should NOT have any tool results - the errored turn's tool calls are ignored + expect(out.some((m) => m.role === "toolResult")).toBe(false); + expect(out.map((m) => m.role)).toEqual(["user", "assistant", "user"]); + }); }); diff --git a/src/agents/session-transcript-repair.ts b/src/agents/session-transcript-repair.ts index e608a8d73..825ceebec 100644 --- a/src/agents/session-transcript-repair.ts +++ b/src/agents/session-transcript-repair.ts @@ -8,6 +8,14 @@ type ToolCallLike = { function extractToolCallsFromAssistant( msg: Extract, ): ToolCallLike[] { + // Skip extracting tool calls if the assistant turn errored/terminated. + // Errored turns contain incomplete tool calls (often with partialJson) that + // were never executed. Inserting synthetic tool_results for these would cause + // Anthropic's API to reject subsequent requests with "unexpected tool_use_id". + // See: https://github.com/clawdbot/clawdbot/issues/1826 + const stopReason = (msg as { stopReason?: unknown }).stopReason; + if (stopReason === "error") return []; + const content = msg.content; if (!Array.isArray(content)) return [];