Skip to content

fix(session-repair): strip malformed tool_use blocks to prevent permanent session corruption#6681

Closed
NSEvent wants to merge 4 commits intoopenclaw:mainfrom
NSEvent:fix/session-corruption-malformed-tool-use
Closed

fix(session-repair): strip malformed tool_use blocks to prevent permanent session corruption#6681
NSEvent wants to merge 4 commits intoopenclaw:mainfrom
NSEvent:fix/session-corruption-malformed-tool-use

Conversation

@NSEvent
Copy link

@NSEvent NSEvent commented Feb 1, 2026

Summary

  • Strips malformed tool_use/toolCall/functionCall blocks from assistant messages BEFORE the existing pairing repair runs
  • Adds droppedMalformedToolUseCount to the repair report for observability
  • Prevents creating synthetic error results for blocks that were never valid tool calls

Problem

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 blocks
  • tool 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:

  • Missing or empty id field (tool call wasn't fully initialized)
  • Has partialJson field present (Anthropic SDK streaming artifact) - uses property presence check ("partialJson" in rec) to catch regardless of value
  • Has partial field set to true (generic streaming indicator)
  • Has incomplete field set to true (OpenAI-style indicator)

The name field is intentionally NOT required - extractToolCallsFromAssistant already handles missing names gracefully by defaulting to undefined.

Design decisions

  • Property presence vs value check: For partialJson, we use "partialJson" in rec rather than !== undefined because the mere presence of this field (even if explicitly undefined) indicates a streaming artifact.
  • Strict boolean checks for partial/incomplete: We use === true rather than truthy checks to avoid false positives from falsy values like 0, "", or null which don't indicate a partial tool call.
  • Expanded logging: All non-zero repair counters are now logged (malformed stripped, orphans dropped, duplicates dropped, synthetic results added) for easier debugging.

Test plan

  • Added comprehensive tests for malformed block detection
  • Existing tests pass (pnpm test src/agents/session-transcript-repair.test.ts)
  • Full test suite passes (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 like partialJson/partial/incomplete) before running the existing tool-call/tool-result pairing repair. It extends the repair report with droppedMalformedToolUseCount for observability, updates the Google embedded runner to use the richer repairToolUseResultPairing report 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

  • This PR is generally safe to merge and should reduce session-corruption failures, with a small compatibility risk around unhandled tool block type variants.
  • Changes are localized to transcript sanitization, preserve message ordering/metadata, and include comprehensive unit tests for the new malformed-block stripping behavior. The main residual risk is that isValidToolUseBlock only recognizes camelCase tool block type values; if other providers store snake_case tool blocks in transcripts, the fix may not apply for those sessions.
  • src/agents/session-transcript-repair.ts (type matching for tool blocks), src/agents/session-transcript-repair.test.ts (coverage for any additional tool block variants used in production)

NSEvent and others added 4 commits January 31, 2026 09:21
…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>
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>
@openclaw-barnacle openclaw-barnacle bot added the agents Agent runtime and tooling label Feb 1, 2026
Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +34 to +36
if (rec.type !== "toolCall" && rec.type !== "toolUse" && rec.type !== "functionCall") {
return true;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +159 to +166
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" },
],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Orphaned tool_result after mid-stream assistant error causes permanent session breakage

1 participant