fix: drop errored/aborted assistant tool pairs in transcript repair#9416
fix: drop errored/aborted assistant tool pairs in transcript repair#9416xandorklein 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().
| // 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; |
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.…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
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. |
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.
|
This pull request has been automatically marked as stale due to inactivity. |
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!