Skip to content

Agents: fix orphaned toolResult after history truncation#5738

Closed
henryjrobinson wants to merge 1 commit intoopenclaw:mainfrom
henryjrobinson:fix/orphaned-tool-result-truncation
Closed

Agents: fix orphaned toolResult after history truncation#5738
henryjrobinson wants to merge 1 commit intoopenclaw:mainfrom
henryjrobinson:fix/orphaned-tool-result-truncation

Conversation

@henryjrobinson
Copy link

@henryjrobinson henryjrobinson commented Jan 31, 2026

Summary

Fixes the "unexpected tool_use_id found in tool_result blocks" API error that occurs when limitHistoryTurns() truncates conversation history.

  • When dmHistoryLimit config is set, history truncation could leave orphaned toolResult messages whose matching toolCall was in the truncated portion
  • This caused Anthropic API to reject the request with validation errors
  • Fix adds detection and removal of orphaned toolResult messages after truncation

Changes

  • Added helper functions in history.ts to extract tool call IDs and detect orphans
  • Modified limitHistoryTurns() to call dropOrphanedToolResults() after truncation
  • Added 3 new test cases covering the orphan detection scenarios

Test plan

  • All 12 limitHistoryTurns tests pass
  • pnpm build succeeds
  • pnpm lint passes

🤖 Generated with Claude Code

Greptile Overview

Greptile Summary

This PR updates limitHistoryTurns() to post-process truncated history and drop toolResult messages whose referenced tool call no longer exists in the kept assistant messages. It adds small helper extractors in history.ts to collect tool call IDs from assistant content blocks and to read tool call IDs from toolResult messages, plus new unit tests that cover orphaned tool results (including toolUse naming).

Confidence Score: 4/5

  • This PR is likely safe to merge and addresses a real API validation failure, with only a small remaining edge case around toolResult blocks with unrecognized/missing IDs.
  • Changes are localized to history truncation logic and backed by new unit tests. The main risk is that the orphan filter intentionally keeps toolResult messages when it can’t extract an ID, which could still allow invalid history through if the toolResult schema differs from what’s expected.
  • src/agents/pi-embedded-runner/history.ts

(2/5) Greptile learns from your feedback when you react with thumbs up/down!

Context used:

  • Context from dashboard - CLAUDE.md (source)
  • Context from dashboard - AGENTS.md (source)

When `limitHistoryTurns()` truncates conversation history (based on
`dmHistoryLimit` config), it could leave orphaned `toolResult` messages
whose matching `toolCall` was in the truncated portion. This caused
Anthropic API errors like:

  "unexpected tool_use_id found in tool_result blocks"

Fix by detecting and dropping orphaned toolResult messages after
truncation. Added helper functions to extract tool call IDs from
assistant messages and filter out toolResults referencing missing IDs.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@openclaw-barnacle openclaw-barnacle bot added the agents Agent runtime and tooling label Jan 31, 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.

1 file reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment on lines +57 to +63
// Filter out toolResult messages that reference missing tool calls
return messages.filter((msg) => {
if (msg.role !== "toolResult") return true;
const id = extractToolResultId(msg);
// Keep if we can't determine the ID (defensive) or if the ID exists
return !id || availableToolCallIds.has(id);
});
Copy link
Contributor

Choose a reason for hiding this comment

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

[P1] dropOrphanedToolResults() currently keeps toolResult messages when it can’t extract an ID (return !id || availableToolCallIds.has(id)). If the API validation error is triggered by a mismatched/unknown tool result identifier field (or a renamed field you didn’t anticipate), this “defensive keep” path can still leave the request invalid after truncation. Consider flipping this to drop tool results with an unrecognized/missing ID when truncation happens (or at least when msg.role === "toolResult"), since a tool result without a resolvable ID can’t be matched anyway.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/pi-embedded-runner/history.ts
Line: 57:63

Comment:
[P1] `dropOrphanedToolResults()` currently *keeps* toolResult messages when it can’t extract an ID (`return !id || availableToolCallIds.has(id)`). If the API validation error is triggered by a mismatched/unknown tool result identifier field (or a renamed field you didn’t anticipate), this “defensive keep” path can still leave the request invalid after truncation. Consider flipping this to drop tool results with an unrecognized/missing ID when truncation happens (or at least when `msg.role === "toolResult"`), since a tool result without a resolvable ID can’t be matched anyway.

How can I resolve this? If you propose a fix, please make it concise.

@sebslight
Copy link
Member

Closing as duplicate of #2557. If this is incorrect, comment and we can reopen.

@sebslight sebslight closed this Feb 13, 2026
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.

2 participants