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 2728bb89c..68c0c935e 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 [];