fix: prevent orphan tool_result errors from streaming failures#3125
fix: prevent orphan tool_result errors from streaming failures#3125snejati86 wants to merge 1 commit intoopenclaw:mainfrom
Conversation
When a streaming error occurs mid-tool-call (e.g., JSON parse error), the tool call gets recorded with `partialJson` and `stopReason: "error"`. Previously, the transcript repair function would insert synthetic tool_results for these incomplete tool calls, but the API rejected them as orphans because the original tool_use block was malformed. This fix: 1. Adds `stripIncompleteToolCalls()` to filter out tool calls with `partialJson` from assistant messages that have `stopReason: "error"` 2. Updates `extractToolCallsFromAssistant()` to skip any tool call block that has `partialJson` property Now when a streaming error happens mid-tool-call: - The incomplete tool call is stripped from the message - No synthetic tool_result is inserted for it - The API request remains valid Fixes issue where sessions became permanently broken after a single streaming error, requiring manual session file deletion to recover.
| const content = assistant.content; | ||
| if (!Array.isArray(content)) return assistant; | ||
|
|
There was a problem hiding this comment.
[P1] stripIncompleteToolCalls() removes blocks based only on partialJson !== undefined, which will also drop tool calls that have partialJson: null or any other non-incomplete sentinel. If partialJson can be present but falsy/cleared in some transcripts, this will incorrectly discard valid tool calls and skip inserting their required results.
If the intent is strictly “incomplete parse”, consider checking for a string (or non-empty) value, e.g. typeof partialJson === "string" && partialJson.length > 0, and/or keying off the actual block type shape used by your transcript format.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/session-transcript-repair.ts
Line: 76:78
Comment:
[P1] `stripIncompleteToolCalls()` removes blocks based only on `partialJson !== undefined`, which will also drop tool calls that have `partialJson: null` or any other non-incomplete sentinel. If `partialJson` can be present but falsy/cleared in some transcripts, this will incorrectly discard valid tool calls and skip inserting their required results.
If the intent is strictly “incomplete parse”, consider checking for a string (or non-empty) value, e.g. `typeof partialJson === "string" && partialJson.length > 0`, and/or keying off the actual block type shape used by your transcript format.
How can I resolve this? If you propose a fix, please make it concise.| * Keeping them causes orphan tool_result errors with the API since the | ||
| * tool_use block is malformed but a synthetic tool_result gets inserted. | ||
| */ | ||
| function stripIncompleteToolCalls( |
There was a problem hiding this comment.
[P3] stripIncompleteToolCalls() flags isErrorResponse if errorMessage is present, regardless of whether it’s actually an error string. If some providers attach errorMessage metadata even on non-error completions, this will start stripping tool calls unexpectedly.
A tighter guard (e.g., stopReason === "error" or typeof errorMessage === "string") would reduce the chance of removing tool calls from legitimate assistant turns.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/session-transcript-repair.ts
Line: 70:73
Comment:
[P3] `stripIncompleteToolCalls()` flags `isErrorResponse` if `errorMessage` is present, regardless of whether it’s actually an error string. If some providers attach `errorMessage` metadata even on non-error completions, this will start stripping tool calls unexpectedly.
A tighter guard (e.g., `stopReason === "error"` or `typeof errorMessage === "string"`) would reduce the chance of removing tool calls from legitimate assistant turns.
How can I resolve this? If you propose a fix, please make it concise.
LedMeIn
left a comment
There was a problem hiding this comment.
Encountered exactly this issue. The fix is correct and needed. +1 for merge.
|
Encountered exactly this issue today. Session became permanently broken after a streaming error (stopReason: error, errorMessage: terminated) mid-tool-call. The synthetic tool_result was inserted for the malformed tool_use, breaking all subsequent API calls. This fix correctly addresses the root cause. +1 for merge! |
|
Closing as duplicate of #5822. If this is incorrect, comment and we can reopen. |
Problem
When a streaming error occurs mid-tool-call (e.g., JSON parse error like
"Unexpected non-whitespace character after JSON at position 1483"), the tool call gets recorded in the session history with:partialJsonfield (indicating incomplete parsing)stopReason: "error"errorMessagedescribing the parse failureThe transcript repair function (
repairToolUseResultPairing) would then:tool_resultfor itHowever, the Anthropic API rejected these requests with:
This happened because the original
tool_useblock was malformed/incomplete, but we were inserting atool_resultfor it anyway.The session became permanently broken - every subsequent message would fail with the same error, requiring manual deletion of the session file to recover.
Solution
This fix adds two safeguards:
stripIncompleteToolCalls()- New function that filters out tool calls withpartialJsonfrom assistant messages that havestopReason: "error"Skip incomplete tool calls in
extractToolCallsFromAssistant()- Added check to skip any tool call block that has thepartialJsonpropertyNow when a streaming error happens mid-tool-call:
tool_resultis inserted for itTesting
Changes
src/agents/session-transcript-repair.ts: AddedstripIncompleteToolCalls()function and updatedrepairToolUseResultPairing()to use itGreptile Overview
Greptile Summary
This PR hardens transcript repair against mid-stream tool-call parse failures by (1) skipping tool-call content blocks that contain a
partialJsonmarker during extraction, and (2) stripping such incomplete tool calls from assistant turns that represent an error response before attempting to re-pair tool_use/tool_result messages. This prevents the repair logic from synthesizingtool_resultblocks for malformed tool calls, which previously could permanently corrupt a session by producing orphantool_resultIDs rejected by Anthropic-compatible APIs.Confidence Score: 4/5
partialJson !== undefinedheuristic could be overly broad ifpartialJsonis present in non-incomplete tool calls (e.g., null/empty sentinel), potentially causing valid tool calls to be skipped/stripped.(2/5) Greptile learns from your feedback when you react with thumbs up/down!