fix(session-repair): strip malformed tool_use blocks to prevent permanent session corruption#6681
Conversation
…uption When tool calls are interrupted (by error, timeout, content filtering, or process termination), sessions can become permanently corrupted. Every subsequent API request fails with errors like: - "unexpected tool_use_id found in tool_result blocks" - "tool result's tool id not found (2013)" Root cause: extractToolCallsFromAssistant() skips malformed tool_use blocks but leaves them in the message content. The blocks remain in the transcript causing API rejections. Fix: Strip malformed tool_use blocks (missing id, missing name, or with partialJson field) BEFORE the pairing repair runs. This prevents creating synthetic results for invalid blocks and allows sessions to auto-recover. Fixes openclaw#5497, openclaw#5481, openclaw#5430, openclaw#5518 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
… assistant messages
Add additional streaming/partial indicators beyond partialJson: - partial === true (generic streaming indicator) - incomplete === true (OpenAI-style indicator) This ensures we catch malformed tool_use blocks from all provider SDK shapes, not just Anthropic's partialJson field. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
P1 fix: Use property presence check ("partialJson" in rec) instead of
`!== undefined` to correctly detect the streaming artifact field
regardless of its value.
P3 fix: Expand logging to include all non-zero repair counters
(malformed stripped, orphans dropped, duplicates dropped, synthetic
results added) for easier debugging of transcript issues.
Added docstring explaining why partial/incomplete use strict boolean
checks (to avoid false positives from falsy values like 0 or "").
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
| if (rec.type !== "toolCall" && rec.type !== "toolUse" && rec.type !== "functionCall") { | ||
| return true; | ||
| } |
There was a problem hiding this comment.
isValidToolUseBlock treats only type: "toolCall" | "toolUse" | "functionCall" as tool blocks. If the repo also serializes Anthropic-style tool_use blocks (snake_case) in transcripts, those malformed blocks (missing id / partial indicators) will not be stripped and the original corruption can persist.
This matters when sessions come from providers/SDKs that emit type: "tool_use"/"tool_result" rather than the camelCase variants. Consider either handling the snake_case types too, or documenting/enforcing that transcripts are normalized to camelCase before this repair runs.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/session-transcript-repair.ts
Line: 34:36
Comment:
`isValidToolUseBlock` treats only `type: "toolCall" | "toolUse" | "functionCall"` as tool blocks. If the repo also serializes Anthropic-style `tool_use` blocks (snake_case) in transcripts, those malformed blocks (missing id / partial indicators) will *not* be stripped and the original corruption can persist.
This matters when sessions come from providers/SDKs that emit `type: "tool_use"`/`"tool_result"` rather than the camelCase variants. Consider either handling the snake_case types too, or documenting/enforcing that transcripts are normalized to camelCase before this repair runs.
How can I resolve this? If you propose a fix, please make it concise.| it("strips tool_use blocks with partialJson field", () => { | ||
| const input = [ | ||
| { | ||
| role: "assistant", | ||
| content: [ | ||
| { type: "toolCall", id: "call_1", name: "read", arguments: {}, partialJson: '{"foo":' }, | ||
| { type: "text", text: "done" }, | ||
| ], |
There was a problem hiding this comment.
The object literal in this test exceeds typical line-length/readability ({ type: "toolCall", id: "call_1", ... partialJson: ... } all on one line). It’s harder to scan compared to the other cases; consider formatting it across multiple lines for consistency.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/session-transcript-repair.test.ts
Line: 159:166
Comment:
The object literal in this test exceeds typical line-length/readability (`{ type: "toolCall", id: "call_1", ... partialJson: ... }` all on one line). It’s harder to scan compared to the other cases; consider formatting it across multiple lines for consistency.
<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>
How can I resolve this? If you propose a fix, please make it concise.
Summary
tool_use/toolCall/functionCallblocks from assistant messages BEFORE the existing pairing repair runsdroppedMalformedToolUseCountto the repair report for observabilityProblem
When tool calls are interrupted (by error, timeout, content filtering, or process termination), sessions become permanently corrupted. Every subsequent API request fails with:
unexpected tool_use_id found in tool_result blockstool result's tool id not found (2013)Root cause: The existing
extractToolCallsFromAssistant()skips malformed blocks (missing id) but leaves them in the message content. The blocks remain in the transcript, causing API rejections.Solution
Add a pre-processing step that strips malformed tool_use blocks before the pairing repair runs:
Malformed conditions detected:
idfield (tool call wasn't fully initialized)partialJsonfield present (Anthropic SDK streaming artifact) - uses property presence check ("partialJson" in rec) to catch regardless of valuepartialfield set totrue(generic streaming indicator)incompletefield set totrue(OpenAI-style indicator)The
namefield is intentionally NOT required -extractToolCallsFromAssistantalready handles missing names gracefully by defaulting toundefined.Design decisions
partialJson, we use"partialJson" in recrather than!== undefinedbecause the mere presence of this field (even if explicitlyundefined) indicates a streaming artifact.=== truerather than truthy checks to avoid false positives from falsy values like0,"", ornullwhich don't indicate a partial tool call.Test plan
pnpm test src/agents/session-transcript-repair.test.ts)pnpm test)Fixes #5497, #5481, #5430, #5518
🤖 Generated with Claude Code
Greptile Overview
Greptile Summary
This PR hardens session transcript repair by stripping malformed assistant tool call blocks (missing/empty
id, or streaming/partial indicators likepartialJson/partial/incomplete) before running the existing tool-call/tool-result pairing repair. It extends the repair report withdroppedMalformedToolUseCountfor observability, updates the Google embedded runner to use the richerrepairToolUseResultPairingreport and logs non-zero counters, and adds targeted tests that reproduce the “permanently corrupted session” scenarios described in the linked issues.Confidence Score: 4/5
isValidToolUseBlockonly recognizes camelCase tool blocktypevalues; if other providers store snake_case tool blocks in transcripts, the fix may not apply for those sessions.