openclaw - ✅(Solved) Fix fix(compaction): splitMessagesByTokenShare can orphan tool_use from toolResult [3 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#58836Fetched 2026-04-08 02:32:05
View on GitHub
Comments
0
Participants
1
Timeline
5
Reactions
0
Participants
Timeline (top)
cross-referenced ×3closed ×1locked ×1

Fix Action

Fixed

PR fix notes

PR #58844: fix(compaction): prevent splitting mid tool-call pairs

Description (problem / solution / changelog)

Fixes #58836

Summary

splitMessagesByTokenShare() splits purely by token count, which can orphan tool_use blocks from their toolResult responses, producing invalid message sequences that cause API errors downstream.

  • Add findCompactionBoundaries() that identifies safe split points (after complete tool_use/toolResult pairs)
  • Add snapToBoundary() to snap proposed split indices to nearest safe boundary
  • Update splitMessagesByTokenShare() to use boundary-aware splitting

Test plan

  • Unit tests for findCompactionBoundaries (empty, single, multiple pending tool calls)
  • Unit tests for snapToBoundary (backward/forward search, no boundary)
  • Integration test: split with tool-call messages verifies no orphaned tool_use
  • Edge case: boundary coincides with exact proposed split point
  • Existing compaction.test.ts passes (10 tests)

Changed files

  • src/agents/compaction-boundary.test.ts (added, +238/-0)
  • src/agents/compaction-boundary.ts (added, +77/-0)
  • src/agents/compaction.ts (modified, +24/-4)

PR #58849: fix(agents): keep tool_use and toolResult together when splitting messages

Description (problem / solution / changelog)

Summary

  • Problem: splitMessagesByTokenShare in src/agents/compaction.ts splits message arrays purely by token count. This can cause a split to land exactly between an assistant message containing a tool_use block and its corresponding toolResult message. This results in chunks with orphaned tool calls or orphaned tool results, which are rejected by strict APIs (like Anthropic) during summarizeInStages.
  • Root Cause: The split decision (currentTokens + messageTokens > targetTokens) did not consider the semantic boundary between tool calls and their results. It blindly cut the array, breaking the invariant that a toolResult must immediately follow its matching assistant turn.
  • Fix: Introduced a pendingToolCallIds set to track tool call boundaries during the iteration. When an assistant message contains tool calls, the split is deferred until all matching toolResult messages are consumed. This guarantees that tool_use and toolResult pairs are never separated into different chunks.
  • What changed:
    • src/agents/compaction.ts: Added boundary tracking logic in splitMessagesByTokenShare.
    • src/agents/compaction.test.ts: Added two regression tests to verify that tool_use and toolResult pairs (including multiple results for a single assistant turn) stay in the same chunk.
  • What did NOT change (scope boundary): Did not modify chunkMessagesByMaxTokens (which operates on messages that have already had tool result details stripped), and did not change the downstream repair logic in session-transcript-repair.ts.

Reproduction

  1. Have a conversation where the agent makes a tool call, and the tool returns a very large result.
  2. The context grows large enough to trigger summarizeInStages.
  3. splitMessagesByTokenShare cuts the history into chunks. If the token boundary falls right after the assistant tool call but before the toolResult, the resulting chunk sent to the LLM for summarization contains an orphaned tool_use, causing an API rejection (e.g., "unexpected tool_use_id").

Risk / Mitigation

  • Risk: Deferring splits could cause a chunk to exceed its token target if a tool result is massive.
  • Mitigation: This is acceptable and handled by downstream code. splitMessagesByTokenShare is a soft-split for summarization parts. The actual summarization call (summarizeChunks) already uses chunkMessagesByMaxTokens as a hard limit and strips tool result details before sending to the LLM, so a slightly larger logical chunk here will not cause context window overflows.

Change Type (select all)

  • Bug fix

Scope (select all touched areas)

  • Agents

Linked Issue/PR

Fixes #58836

Changed files

  • src/agents/compaction.test.ts (modified, +96/-13)
  • src/agents/compaction.ts (modified, +42/-0)

PR #58855: fix(compaction): prevent splitting mid tool-call pairs

Description (problem / solution / changelog)

Fixes #58836

Summary

splitMessagesByTokenShare() splits purely by token count, which can orphan tool_use blocks from their toolResult responses, producing invalid message sequences that cause API errors downstream.

  • Add findCompactionBoundaries() that identifies safe split points (after complete tool_use/toolResult pairs)
  • Add snapToBoundary() to snap proposed split indices to nearest safe boundary
  • Update splitMessagesByTokenShare() to use boundary-aware splitting

Test plan

  • Unit tests for findCompactionBoundaries (empty, single, multiple pending tool calls)
  • Unit tests for snapToBoundary (backward/forward search, no boundary)
  • Integration test: split with tool-call messages verifies no orphaned tool_use
  • Edge case: boundary coincides with exact proposed split point
  • Existing compaction.test.ts passes (10 tests)
  • tsgo --noEmit clean, oxlint clean, oxfmt --check clean

Changed files

  • src/agents/compaction-boundary.test.ts (added, +222/-0)
  • src/agents/compaction-boundary.ts (added, +77/-0)
  • src/agents/compaction.test.ts (modified, +19/-28)
  • src/agents/compaction.ts (modified, +33/-4)

Code Example

[user] (1000 tokens)
[assistant + tool_use: tc1] (1000 tokens)
[toolResult: tc1] (200 tokens)
[user] (1000 tokens)
RAW_BUFFERClick to expand / collapse

Bug Description

splitMessagesByTokenShare() in src/agents/compaction.ts splits message arrays purely by token count. When a split point lands between an assistant message containing a tool_use block and its corresponding toolResult message, the resulting chunks contain orphaned messages that produce invalid API request sequences.

Reproduction

Given a conversation with interleaved tool calls:

[user] (1000 tokens)
[assistant + tool_use: tc1] (1000 tokens)
[toolResult: tc1] (200 tokens)
[user] (1000 tokens)

When splitMessagesByTokenShare(messages, 2) is called with a target of ~1600 tokens per chunk, the split can land between the assistant (index 1) and toolResult (index 2), producing:

  • Chunk 1: [user, assistant+tool_use:tc1] — orphaned tool_use with no result
  • Chunk 2: [toolResult:tc1, user] — orphaned toolResult with no matching tool_use

Impact

  • Downstream summarization receives invalid message sequences
  • Anthropic API rejects orphaned tool_use_id references
  • repairToolUseResultPairing() in pruneHistoryForContextShare already handles this for pruning, but splitMessagesByTokenShare lacks equivalent protection

Expected Behavior

Split points should respect tool call boundaries — never splitting between a tool_use and its matching toolResult. The split should snap to the nearest safe boundary (after a complete tool call/result pair).

Affected Code

  • src/agents/compaction.ts:116splitMessagesByTokenShare()
  • Called from summarizeInStages() (line 424) and pruneHistoryForContextShare() (line 487)

extent analysis

TL;DR

Modify the splitMessagesByTokenShare() function to prioritize splitting at safe boundaries, ensuring that tool_use and toolResult pairs are not separated.

Guidance

  • Identify the indices of tool_use and corresponding toolResult messages in the input array to determine safe split points.
  • Implement a check to prevent splits between tool_use and toolResult pairs, adjusting the split point to the nearest safe boundary.
  • Consider adding a test case to verify the corrected behavior with interleaved tool calls.
  • Review the repairToolUseResultPairing() function in pruneHistoryForContextShare for inspiration on handling tool call boundaries.

Example

// Pseudocode example, actual implementation may vary
function splitMessagesByTokenShare(messages, targetTokenCount) {
  const safeSplitPoints = [];
  for (let i = 0; i < messages.length; i++) {
    if (isToolUseMessage(messages[i]) && i + 1 < messages.length && isToolResultMessage(messages[i + 1])) {
      // Skip splitting between tool_use and toolResult pairs
      continue;
    }
    // Calculate token count and check if it's a safe split point
    safeSplitPoints.push(i);
  }
  // Adjust split points based on target token count and safe boundaries
}

Notes

The exact implementation details may depend on the specific requirements and constraints of the splitMessagesByTokenShare() function, such as handling edge cases or optimizing performance.

Recommendation

Apply a workaround by modifying the splitMessagesByTokenShare() function to respect tool call boundaries, as this will directly address the issue and prevent orphaned messages.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - ✅(Solved) Fix fix(compaction): splitMessagesByTokenShare can orphan tool_use from toolResult [3 pull requests, 1 participants]