← Back to PRs

#9416: fix: drop errored/aborted assistant tool pairs in transcript repair

by xandorklein open 2026-02-05 06:13 View on GitHub →
agents stale
## Problem 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 from the session file. ## 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. ## Test plan - [x] 4 new test cases: - drops errored assistant with tool calls and its tool results - drops aborted assistant with tool calls and its tool results - keeps errored assistant without tool calls (no change to existing behavior) - preserves remainder messages when dropping errored assistant - [x] All 10 existing + new tests pass ## Note This is also a bug in `pi-ai`'s `transformMessages()` — 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_comment --> <h2>Greptile Overview</h2> <h3>Greptile Summary</h3> This PR updates transcript repair (`repairToolUseResultPairing`) to detect assistant messages that stopped with `stopReason: "error" | "aborted"` *and* contain tool calls, and then drop that assistant turn along with its associated tool results. This prevents downstream message transformation (in `pi-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. <h3>Confidence Score: 4/5</h3> - This PR is close to safe to merge, with one correctness concern around duplicate toolResult handling in the new drop branch. - Core change is narrowly scoped and well-covered by new tests, but the new branch drops tool results without recording their IDs in the global duplicate tracker, which can let later duplicates through in transcripts that contain repeated tool result IDs. - src/agents/session-transcript-repair.ts <!-- greptile_other_comments_section --> <sub>(3/5) Reply to the agent's comments like "Can you suggest a fix for this @greptileai?" or ask follow-up questions!</sub> <!-- /greptile_comment -->

Most Similar PRs