Skip to content

fix(agents): re-run tool_use/tool_result repair after limitHistoryTurns truncation#13974

Closed
zerone0x wants to merge 2 commits intoopenclaw:mainfrom
zerone0x:fix/tool-use-orphan-after-limit-history-turns
Closed

fix(agents): re-run tool_use/tool_result repair after limitHistoryTurns truncation#13974
zerone0x wants to merge 2 commits intoopenclaw:mainfrom
zerone0x:fix/tool-use-orphan-after-limit-history-turns

Conversation

@zerone0x
Copy link
Contributor

@zerone0x zerone0x commented Feb 11, 2026

Summary

Fixes #13896

limitHistoryTurns() can orphan tool_result blocks when it slices off the assistant message containing the corresponding tool_use. This causes HTTP 400 errors from the Anthropic API:

messages.14.content.1: unexpected tool_use_id found in tool_result blocks:
toolu_01JRe7vPiWiFCUPwUaYWKyaF

Root cause

The message preparation pipeline runs sanitizeSessionHistory() (which calls repairToolUseResultPairing()) before limitHistoryTurns(). When limitHistoryTurns() then slices the history by user-turn count, it can remove an assistant message containing a tool_use block while keeping the corresponding tool_result in a later user message — re-introducing orphaned tool results after the repair already ran.

Fix

Re-run sanitizeToolUseResultPairing() after limitHistoryTurns() in both affected code paths:

  1. Compaction flow (compact.ts)
  2. Main reply flow (run/attempt.ts)

The repair is gated on the same transcriptPolicy.repairToolUseResultPairing flag used by the existing pre-truncation repair, so it only runs for providers that need it (Anthropic, Google).

This follows the same pattern already used in compaction.ts:339-357 where repairToolUseResultPairing() is called after dropping message chunks.

Changes

  • src/agents/pi-embedded-runner/compact.ts — add post-truncation sanitizeToolUseResultPairing() call
  • src/agents/pi-embedded-runner/run/attempt.ts — add post-truncation sanitizeToolUseResultPairing() call
  • src/agents/pi-embedded-runner.limithistoryturns.test.ts — add regression tests for the orphan scenario

Test plan

  • New tests verify limitHistoryTurns + sanitizeToolUseResultPairing drops orphaned tool_results
  • All existing tests pass (40/40 across 4 related test files)
  • Lint passes (oxlint)
  • Format passes (oxfmt)
  • Build succeeds (tsdown)

🤖 Generated with Claude Code (issue-hunter-pro)

Greptile Overview

Greptile Summary

Fixes orphaned tool_result blocks that occur when limitHistoryTurns() slices off assistant messages containing tool_use blocks while keeping their corresponding tool_result in later messages. This was causing HTTP 400 errors from the Anthropic API with "unexpected tool_use_id found in tool_result blocks" errors.

The fix re-runs sanitizeToolUseResultPairing() after limitHistoryTurns() in both the compaction flow (compact.ts) and main reply flow (attempt.ts). The repair is gated on the transcriptPolicy.repairToolUseResultPairing flag, so it only runs for providers that need it (Anthropic, Google).

  • Added post-truncation sanitizeToolUseResultPairing() call in compact.ts:440-442
  • Added post-truncation sanitizeToolUseResultPairing() call in attempt.ts:567-569
  • Added comprehensive regression tests validating the fix
  • Includes unrelated fix to usage.ts that excludes cache tokens from context-window accounting (prevents premature auto-compaction)

Confidence Score: 5/5

  • This PR is safe to merge with minimal risk
  • The fix directly addresses the root cause with a surgical change that follows the existing pattern used elsewhere in the codebase. The implementation is gated by the same flag used in the pre-truncation repair, ensuring it only runs when needed. Comprehensive regression tests validate the fix, and the PR description indicates all existing tests pass (40/40). The usage.ts change is a separate but related fix that improves context-window accounting accuracy.
  • No files require special attention

Yansu and others added 2 commits February 11, 2026 15:44
derivePromptTokens() summed input + cacheRead + cacheWrite, but cache
tokens are already part of the prompt — they don't consume additional
context window capacity.  With Anthropic prompt caching, cacheRead can
easily exceed 100K, inflating the total past the 200K context cap and
triggering premature auto-compaction.

Use only `input` tokens for context-window percentage and compaction
thresholds.  Cache metrics are preserved in NormalizedUsage for cost
reporting.

Fixes openclaw#13853

Co-Authored-By: Claude <[email protected]>
…ns truncation

limitHistoryTurns() can orphan tool_result blocks when it slices off the
assistant message containing the corresponding tool_use. This causes
HTTP 400 errors from the Anthropic API ("unexpected tool_use_id found in
tool_result blocks").

Re-run sanitizeToolUseResultPairing() after limitHistoryTurns() in both
the compaction and reply flows, gated on the same transcript policy flag.

Fixes openclaw#13896

Co-Authored-By: Claude <[email protected]>
@openclaw-barnacle openclaw-barnacle bot added the agents Agent runtime and tooling label Feb 11, 2026
TWFBusiness added a commit to TWFBusiness/openclaw-tw that referenced this pull request Feb 11, 2026
…pstream fixes

- Remove Discord, Telegram, Slack, Signal, iMessage, BlueBubbles extensions
- Remove @buape/carbon, grammy, and other gateway-specific dependencies
- Add session-based login system (cookie auth, rate limiting)
- Add type stubs for optional deps (baileys, ciao, qrcode-terminal)
- Apply upstream PRs: XSS fix (openclaw#13952), orphan tool_results (openclaw#13974),
  Anthropic tool ID sanitization (openclaw#13976), BM25 score fix (openclaw#14005),
  redaction fix (openclaw#14006), subagent timeout (openclaw#13996), context window
  guard (openclaw#13986), model default reset (openclaw#13993), cache token accounting (openclaw#13853)
- Fix all TypeScript compilation errors from channel removal

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@zerone0x
Copy link
Contributor Author

🤖 Closing: the fix for limitHistoryTurns orphaning tool_result blocks is already in main (commit 50b7607). The sanitizeToolUseResultPairing call after truncation was added to both compact.ts and attempt.ts.

@zerone0x zerone0x closed this Feb 15, 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.

tool_use_id orphan after limitHistoryTurns truncation causes HTTP 400

1 participant

Comments