fix(agents): skip error/aborted assistant messages in transcript repair#4844
fix(agents): skip error/aborted assistant messages in transcript repair#4844lailoo wants to merge 1 commit intoopenclaw:mainfrom
Conversation
When an assistant response is interrupted mid-tool-call (stopReason: error or aborted), repairToolUseResultPairing() was still extracting tool calls and inserting synthetic tool_result entries. This caused orphan tool_result blocks after transformMessages() dropped the errored assistant, leading to permanent session corruption with 'unexpected tool_use_id' API errors. This fix skips tool call extraction for error/aborted assistant messages, preventing the creation of orphan tool_result entries. Fixes openclaw#4600, openclaw#4597, openclaw#4475, openclaw#4814, openclaw#4815
| // Skip error/aborted assistant messages - do not extract tool calls from them | ||
| // as they may contain incomplete/invalid tool_use blocks that would cause | ||
| // orphan tool_result entries after transformMessages() drops the errored assistant | ||
| const stopReason = (assistant as { stopReason?: string }).stopReason; |
There was a problem hiding this comment.
[P1] Skipping errored/aborted assistants can leave their existing tool results as orphans.
This change avoids creating synthetic toolResults for stopReason === "error" | "aborted", but if the transcript already contains real tool results for those tool calls (e.g., assistant streamed toolCall then later got marked error, yet the tool executed and logged a toolResult), the next loop iterations will hit the role !== "assistant" branch and drop those toolResults as “orphan”. That can discard valid tool outputs and potentially change downstream behavior (e.g., losing tool error context).
If that scenario is expected, it likely needs a test and/or a more nuanced rule (e.g., treat errored assistants with complete tool calls differently, or only skip when the assistant content is known-incomplete).
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/session-transcript-repair.ts
Line: 120:123
Comment:
[P1] Skipping errored/aborted assistants can leave their *existing* tool results as orphans.
This change avoids creating synthetic `toolResult`s for `stopReason === "error" | "aborted"`, but if the transcript already contains real tool results for those tool calls (e.g., assistant streamed `toolCall` then later got marked `error`, yet the tool executed and logged a `toolResult`), the next loop iterations will hit the `role !== "assistant"` branch and drop those `toolResult`s as “orphan”. That can discard valid tool outputs and potentially change downstream behavior (e.g., losing tool error context).
If that scenario is expected, it likely needs a test and/or a more nuanced rule (e.g., treat errored assistants with complete tool calls differently, or only skip when the assistant content is known-incomplete).
How can I resolve this? If you propose a fix, please make it concise.| it("skips tool call extraction for error assistant messages", () => { | ||
| const input = [ | ||
| { | ||
| role: "assistant", | ||
| stopReason: "error", | ||
| errorMessage: "Request terminated", |
There was a problem hiding this comment.
[P2] New tests only assert “no synthetic toolResult inserted”, but don’t cover the case where a real toolResult already exists.
Given the production bug involves interactions with transformMessages() dropping errored assistants, it would be useful to add a test where an assistant(stopReason:error) with a tool call is followed by an actual matching toolResult entry, and verify the intended behavior (drop it? keep it? move it?). Without that, the behavior change mentioned in session-transcript-repair.ts can regress silently.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/session-transcript-repair.test.ts
Line: 113:118
Comment:
[P2] New tests only assert “no synthetic toolResult inserted”, but don’t cover the case where a *real* toolResult already exists.
Given the production bug involves interactions with `transformMessages()` dropping errored assistants, it would be useful to add a test where an `assistant(stopReason:error)` with a tool call is followed by an actual matching `toolResult` entry, and verify the intended behavior (drop it? keep it? move it?). Without that, the behavior change mentioned in `session-transcript-repair.ts` can regress silently.
How can I resolve this? If you propose a fix, please make it concise.|
👋 This appears to duplicate PR #4476, which implements the same fix. Consider consolidating or closing one to avoid duplicate effort. |
bfc1ccb to
f92900f
Compare
|
AI-assisted stale triage closure (2026-02-24). Closing this PR because the fix is already in Why:
This is AI-closed housekeeping, not a rejection of the issue. If you still see orphan tool-result behavior after error/abort paths, open a fresh focused PR from latest |
Summary
When an assistant response is interrupted mid-tool-call (
stopReason: errororaborted),repairToolUseResultPairing()was still extracting tool calls and inserting synthetictool_resultentries. This caused orphantool_resultblocks aftertransformMessages()dropped the errored assistant, leading to permanent session corruption withunexpected tool_use_idAPI errors.Changes
repairToolUseResultPairing()insrc/agents/session-transcript-repair.tsto skip tool call extraction forstopReason === "error" || "aborted"assistant messagesRoot Cause
Two safety mechanisms interacted badly:
session-tool-result-guardinserts synthetictoolResultfor orphaned tool callstransformMessages()drops error/aborted assistant messages but not their correspondingtoolResultThis created orphaned
tool_resultentries that referenced non-existenttool_useblocks.Testing
session-transcript-repair.test.tspasssession-tool-result-guard.test.tsandpi-embedded-runner.guard.test.tspassFixes
Greptile Overview
Greptile Summary
This PR updates
repairToolUseResultPairing()to avoid extracting tool calls (and thus avoid inserting synthetictoolResultentries) from assistant messages withstopReason: "error"or"aborted". This prevents creating orphantoolResultblocks later when other sanitization (transformMessages()) drops those assistant turns but leaves tool results behind.Unit tests were added to ensure error/aborted assistant messages do not trigger synthetic
toolResultinsertion, while normal tool-using assistants are still repaired as before.Confidence Score: 4/5