fix(session-repair): strip malformed tool_use blocks to prevent permanent session corruption#6687
Closed
NSEvent wants to merge 6 commits intoopenclaw:mainfrom
Closed
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>
…rmatting Address Greptile feedback: - Add snake_case type variants (tool_use, function_call) to support sessions from providers/SDKs that emit these types - Format test object literal across multiple lines for readability Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Address Greptile P1: Both `isValidToolUseBlock` and `extractToolCallsFromAssistant` now use a shared `TOOL_BLOCK_TYPES` Set to ensure consistent handling of all tool block type variants. This ensures snake_case tool blocks (tool_use, function_call) are properly extracted for pairing repair, not just stripped. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
077e721 to
a6f6ce6
Compare
4 tasks
bfc1ccb to
f92900f
Compare
Contributor
|
AI-assisted stale triage closure (2026-02-24). Closing this PR because the fix is already superseded by merged work. Why:
This is AI-closed housekeeping, not a rejection of corruption-prevention concerns. If malformed tool_use blocks still corrupt sessions on current |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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)Type variants supported (via shared
TOOL_BLOCK_TYPESSet):toolCall,toolUse,functionCalltool_use,function_callBoth
isValidToolUseBlockandextractToolCallsFromAssistantuse the sameTOOL_BLOCK_TYPESSet to ensure consistent handling across validation and extraction.The
namefield is intentionally NOT required -extractToolCallsFromAssistantalready handles missing names gracefully by defaulting toundefined.Design decisions
TOOL_BLOCK_TYPESis a Set used by both functions to ensure consistency.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)pnpm lint)Fixes #5497, #5481, #5430, #5518
🤖 Generated with Claude Code
Greptile Overview
Greptile Summary
This PR hardens session transcript repair by stripping malformed assistant tool blocks (e.g., missing/empty
idor streaming artifacts likepartialJson,partial: true,incomplete: true) before the existing tool-call/tool-result pairing logic runs. It also unifies tool-block type detection across validation and extraction via a sharedTOOL_BLOCK_TYPESset (supporting both camelCase and snake_case variants), addsdroppedMalformedToolUseCountto the repair report for observability, and updates the Google embedded runner to log non-zero repair counters.The change integrates cleanly with existing transcript sanitation:
sanitizeToolUseResultPairing()now delegates torepairToolUseResultPairing(), which first cleans assistant content and then enforces strict provider requirements by moving matchingtoolResultmessages directly after the corresponding assistant tool-call turn, dropping orphan/duplicate results, and synthesizing missing results only for valid tool calls.Confidence Score: 5/5
Context used:
dashboard- CLAUDE.md (source)dashboard- AGENTS.md (source)