-
-
Notifications
You must be signed in to change notification settings - Fork 40.3k
Description
Synthetic tool_result for terminated tool_use causes all subsequent requests to fail
Description
When an assistant message contains a tool_use (toolCall) but has stopReason: "error" with errorMessage: "terminated", the session transcript repair mechanism (repairToolUseResultPairing) inserts a synthetic tool_result to maintain the tool_use/tool_result pairing invariant. However, this synthetic result causes all subsequent agent requests to fail with:
invalid_request_error: messages.140.content.1: unexpected `tool_use_id` found in `tool_result` blocks: toolu_01XXX.
Each `tool_result` block must have a corresponding `tool_use` block in the previous message.
The session becomes permanently broken until the synthetic tool_result is manually removed from the JSONL file.
Root Cause
The issue occurs in src/agents/session-transcript-repair.ts in the repairToolUseResultPairing() function:
for (const call of toolCalls) {
const existing = spanResultsById.get(call.id);
if (existing) {
pushToolResult(existing);
} else {
const missing = makeMissingToolResult({
toolCallId: call.id,
toolName: call.name,
});
added.push(missing);
changed = true;
pushToolResult(missing);
}
}The problem: When an assistant message has stopReason: "error", it means the turn was never completed. The tool_use within that message is incomplete/invalid (often contains partialJson instead of complete arguments).
However, repairToolUseResultPairing still extracts tool calls from this errored assistant message and inserts synthetic results for them. These synthetic results then fail Anthropic's API validation because:
- The original assistant message's tool_use was never actually "executed" (it was terminated)
- The synthetic tool_result appears in the transcript but references a tool_use that the API considers invalid/incomplete
- Subsequent requests include this broken pairing, causing all future turns to fail
Steps to Reproduce
- Start an agent turn that attempts to use a tool (e.g.,
cron) - Have the agent turn terminate mid-generation (timeout, manual interrupt, error, etc.) with
stopReason: "error"anderrorMessage: "terminated" - The session file will contain:
- Line N: assistant message with partial tool_use +
stopReason: "error" - Line N+1: synthetic tool_result (inserted by repair logic)
- Line N: assistant message with partial tool_use +
- Try to send any new message to the agent
- All requests fail with the "unexpected tool_use_id" error
Example Session Fragment
Line 229 (terminated assistant message):
{
"type": "message",
"id": "5de97cdc",
"message": {
"role": "assistant",
"content": [
{
"type": "toolCall",
"id": "toolu_01S751qdKdkNWi5MbrpvCZvx",
"name": "cron",
"arguments": {
"action": "add",
"job": {
"name": "morning-greeting-xuejie",
"payload": {
"kind": "systemEvent",
"text": "提醒:给"
}
}
},
"partialJson": "{\"action\": \"add\", \"job\": {\"name\": \"morning-greeting-xuejie\", \"payload\": {\"kind\": \"systemEvent\", \"text\": \"\\u63d0\\u9192\\uff1a\\u7ed9"
}
],
"stopReason": "error",
"errorMessage": "terminated"
}
}Line 230 (synthetic result - THIS BREAKS THE SESSION):
{
"type": "message",
"id": "f9df68a5",
"parentId": "5de97cdc",
"message": {
"role": "toolResult",
"toolCallId": "toolu_01S751qdKdkNWi5MbrpvCZvx",
"toolName": "cron",
"content": [
{
"type": "text",
"text": "[clawdbot] missing tool result in session history; inserted synthetic error result for transcript repair."
}
],
"isError": true
}
}All subsequent requests fail:
{
"type": "error",
"error": {
"type": "invalid_request_error",
"message": "messages.140.content.1: unexpected `tool_use_id` found in `tool_result` blocks: toolu_01S751qdKdkNWi5MbrpvCZvx. Each `tool_result` block must have a corresponding `tool_use` block in the previous message."
}
}Expected Behavior
When an assistant message has stopReason: "error":
- Tool calls within that message should be ignored during transcript repair
- No synthetic tool_result should be inserted
- The session should remain usable after the error
Actual Behavior
- Synthetic tool_result is inserted for the incomplete/terminated tool_use
- Session becomes permanently broken
- All future requests fail until manual JSONL editing
Suggested Fix
Modify repairToolUseResultPairing() in src/agents/session-transcript-repair.ts to skip tool call extraction from assistant messages with error stop reasons:
for (let i = 0; i < messages.length; i += 1) {
const msg = messages[i] as AgentMessage;
// ... existing checks ...
if (role !== "assistant") {
// ... existing logic ...
continue;
}
const assistant = msg as Extract<AgentMessage, { role: "assistant" }>;
// NEW: Skip tool call extraction if the assistant turn errored
const stopReason = (assistant as { stopReason?: unknown }).stopReason;
if (stopReason === "error") {
out.push(msg);
continue;
}
const toolCalls = extractToolCallsFromAssistant(assistant);
// ... rest of existing logic ...
}Alternatively, filter out errored tool calls during extraction:
function extractToolCallsFromAssistant(
msg: Extract<AgentMessage, { role: "assistant" }>,
): ToolCallLike[] {
// Skip if the message indicates an error
const stopReason = (msg as { stopReason?: unknown }).stopReason;
if (stopReason === "error") return [];
// ... existing extraction logic ...
}Environment
- Clawdbot version: 2026.1.24-0 (commit 6a9301c)
- Node version: 24.10.0
- Platform: macOS 26.0.1 (arm64)
- Provider: Anthropic (claude-opus-4-5)
Workaround
Manual fix requires:
- Locate the synthetic tool_result in the session JSONL file
- Delete the line containing it
- Update any
parentIdreferences that pointed to the deleted message
This is error-prone and requires understanding the JSONL structure.
Impact
- Severity: High - completely breaks the session until manual intervention
- Frequency: Happens whenever an agent turn is interrupted/terminated during tool use
- User Experience: Confusing error messages, requires manual file editing
Additional Notes
The error message from Anthropic is technically correct - the tool_result references a tool_use that was never successfully completed. The root issue is that Clawdbot's repair logic doesn't account for the semantic difference between "missing tool_result for a completed tool_use" vs "tool_use that never completed execution".