fix: drop errored/aborted assistant tool pairs in transcript repair#9416
Open
xandorklein wants to merge 1 commit intoopenclaw:mainfrom
Open
fix: drop errored/aborted assistant tool pairs in transcript repair#9416xandorklein wants to merge 1 commit intoopenclaw:mainfrom
xandorklein wants to merge 1 commit intoopenclaw:mainfrom
Conversation
pi-ai's transformMessages() strips errored/aborted assistant messages but
keeps their tool results, orphaning tool_result blocks and causing Anthropic
API rejections ('unexpected tool_use_id found in tool_result blocks').
Strip errored/aborted assistants along with their matched tool results in
repairToolUseResultPairing() before messages reach transformMessages().
Comment on lines
+272
to
+280
| // Drop errored/aborted assistants and their tool results entirely. | ||
| if (isErroredAssistant) { | ||
| changed = true; | ||
| droppedOrphanCount += spanResultsById.size; | ||
| for (const rem of remainder) { | ||
| out.push(rem); | ||
| } | ||
| i = j - 1; | ||
| continue; |
Contributor
There was a problem hiding this comment.
Dropping tool results can undercount duplicates
When isErroredAssistant is true, this branch adds spanResultsById.size to droppedOrphanCount but does not update seenToolResultIds. That means any of these dropped tool results may be seen again later in the transcript and treated as “not duplicate”, allowing a later duplicate to survive when it should be dropped. If seenToolResultIds is intended to track all tool result IDs encountered (even dropped ones), you should add these IDs to seenToolResultIds here (or otherwise ensure later duplicates are still dropped).
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/session-transcript-repair.ts
Line: 272:280
Comment:
**Dropping tool results can undercount duplicates**
When `isErroredAssistant` is true, this branch adds `spanResultsById.size` to `droppedOrphanCount` but does **not** update `seenToolResultIds`. That means any of these dropped tool results may be seen again later in the transcript and treated as “not duplicate”, allowing a later duplicate to survive when it should be dropped. If `seenToolResultIds` is intended to track *all* tool result IDs encountered (even dropped ones), you should add these IDs to `seenToolResultIds` here (or otherwise ensure later duplicates are still dropped).
How can I resolve this? If you propose a fix, please make it concise.
hongw
pushed a commit
to hongw/openclaw
that referenced
this pull request
Feb 10, 2026
…repair Port of PR openclaw#9416 - defense against pi-ai transformMessages() bug. When an assistant message with tool calls errors or aborts during execution, the message is persisted with stopReason: "error" (or "aborted") alongside its toolResult messages. On subsequent turns, repairToolUseResultPairing() correctly pairs these tool results with the errored assistant. However, pi-ai's transformMessages() then strips the errored assistant (it skips messages with stopReason === "error" || "aborted") but keeps the orphaned tool results. Those orphaned tool_result blocks get grouped with the preceding assistant's tool_use blocks in the API request, causing: HTTP 400 invalid_request_error: unexpected tool_use_id found in tool_result blocks This is a session-killing error — once triggered, every subsequent message fails with the same 400 until the errored messages are manually removed. Fix: When repairToolUseResultPairing() encounters an assistant message with stopReason === "error" || "aborted" that has tool calls, it now drops both the assistant message and its associated tool results. Non-tool messages in between (remainder) are preserved. This runs before transformMessages(), so the orphaned blocks never reach the API. Fixes openclaw#6158 See also: openclaw#10932, openclaw#12112
hongw
pushed a commit
to hongw/openclaw
that referenced
this pull request
Feb 15, 2026
Cherry-picked from upstream OpenClaw: - PR openclaw#9416: drop errored/aborted assistant tool pairs in transcript repair - PR openclaw#12487: strip orphaned tool_result when tool_use is sanitized on retry - PR openclaw#8243: repair orphaned tool_use blocks on session load Fixes session-killing errors caused by orphaned tool_use/tool_result blocks. When these orphaned blocks reach the API, they cause permanent HTTP 400 errors that break every subsequent message in the session.
bfc1ccb to
f92900f
Compare
|
This pull request has been automatically marked as stale due to inactivity. |
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.
Problem
When an assistant message with tool calls errors or aborts during execution, the message is persisted with
stopReason: "error"(or"aborted") alongside itstoolResultmessages.On subsequent turns,
repairToolUseResultPairing()correctly pairs these tool results with the errored assistant. However,pi-ai'stransformMessages()then strips the errored assistant (it skips messages withstopReason === "error" || "aborted") but keeps the orphaned tool results.Those orphaned
tool_resultblocks get grouped with the preceding assistant'stool_useblocks in the API request, causing:This is a session-killing error — once triggered, every subsequent message fails with the same 400 until the errored messages are manually removed from the session file.
Fix
When
repairToolUseResultPairing()encounters an assistant message withstopReason === "error" || "aborted"that has tool calls, it now drops both the assistant message and its associated tool results. Non-tool messages in between (remainder) are preserved.This runs before
transformMessages(), so the orphaned blocks never reach the API.Test plan
Note
This is also a bug in
pi-ai'stransformMessages()— when stripping errored/aborted assistant messages, it should also strip their associated tool results. This fix handles it at the openclaw layer as a defense-in-depth measure.Greptile Overview
Greptile Summary
This PR updates transcript repair (
repairToolUseResultPairing) to detect assistant messages that stopped withstopReason: "error" | "aborted"and contain tool calls, and then drop that assistant turn along with its associated tool results. This prevents downstream message transformation (inpi-ai) from stripping the assistant while leaving orphaned tool results, which can cause Anthropic-compatible APIs to reject requests due to unexpected tool result IDs.Tests were added to cover dropping errored/aborted tool-call assistants, preserving errored assistants without tool calls, and ensuring non-tool “remainder” messages are preserved when the errored span is removed.
Confidence Score: 4/5
(3/5) Reply to the agent's comments like "Can you suggest a fix for this @greptileai?" or ask follow-up questions!