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 <noreply@anthropic.com>
This commit is contained in:
zerone0x 2026-01-26 00:42:34 +08:00
parent c8063bdcd8
commit 6747541040
2 changed files with 38 additions and 0 deletions

View File

@ -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"]);
});
});

View File

@ -8,6 +8,14 @@ type ToolCallLike = {
function extractToolCallsFromAssistant(
msg: Extract<AgentMessage, { role: "assistant" }>,
): 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 [];