Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Bug: tool_use_id mismatch after limitHistoryTurns truncation #4367

@SocialMDev

Description

@SocialMDev

Bug Description

LLM API requests are rejected with the error:

messages.136.content.1: unexpected tool_use_id found in tool_result blocks: toolu_01Q1BaXmp2s7A2Xqu18Zd9mR. Each tool_result block must have a corresponding tool_use block in the previous message.

Root Cause

In dist/agents/pi-embedded-runner/run/attempt.js, the message processing pipeline has a synchronization bug:

  1. Line 431-439: sanitizeSessionHistory() calls repairToolUseResultPairing() to fix tool_use/tool_result pairings
  2. Line 447: limitHistoryTurns() runs AFTER the repair, truncating conversation history
  3. The truncation can cut between an assistant message (with tool_use) and its tool_result, creating orphaned tool_result blocks that reference non-existent tool_use_ids
  4. The Anthropic API rejects the malformed transcript

Code Location

File: dist/agents/pi-embedded-runner/run/attempt.js (lines 431-451)

const prior = await sanitizeSessionHistory({...}); // Repairs pairings
const validatedGemini = transcriptPolicy.validateGeminiTurns
    ? validateGeminiTurns(prior)
    : prior;
const validated = transcriptPolicy.validateAnthropicTurns
    ? validateAnthropicTurns(validatedGemini)
    : validatedGemini;
const limited = limitHistoryTurns(validated, ...); // BUG: Truncates AFTER repair!

File: dist/agents/pi-embedded-runner/history.js (lines 10-24)

The limitHistoryTurns() function counts user turns backwards and slices at a user message boundary, which can split assistant+tool_result pairs.

Proposed Fix

Call sanitizeToolUseResultPairing() after limitHistoryTurns() to repair any pairings broken by truncation:

import { sanitizeToolUseResultPairing } from "../../session-transcript-repair.js";

// ... existing code ...

const limited = limitHistoryTurns(validated, getDmHistoryLimitFromSessionKey(...));
// Fix: Repair tool_use/tool_result pairings AFTER truncation
const repaired = sanitizeToolUseResultPairing(limited);
cacheTrace?.recordStage("session:limited", { messages: repaired });
if (repaired.length > 0) {
    activeSession.agent.replaceMessages(repaired);
}

Reproduction

  1. Have a long DM conversation with many tool calls
  2. Configure dmHistoryLimit to a value that truncates history
  3. The truncation cuts between an assistant tool_use and its tool_result
  4. Next LLM request fails with "unexpected tool_use_id" error

Environment

  • clawdbot version: 2026.1.24-3
  • Node.js version: 22.x
  • Platform: Linux

Additional Context

The transcript-sanitize.js extension also calls repairToolUseResultPairing() on the context event, but this runs before the truncation in attempt.js, so it doesn't catch pairings broken by limitHistoryTurns().

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions