From 43818e1583641d022cdb856aa0ec4e48c53ed5ee Mon Sep 17 00:00:00 2001 From: 0xRain Date: Thu, 12 Feb 2026 07:42:05 +0800 Subject: [PATCH] fix(agents): re-run tool_use pairing repair after history truncation (#13926) Co-authored-by: 0xRaini <0xRaini@users.noreply.github.com> --- src/agents/pi-embedded-runner/compact.ts | 9 ++++++++- src/agents/pi-embedded-runner/run/attempt.ts | 9 ++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/agents/pi-embedded-runner/compact.ts b/src/agents/pi-embedded-runner/compact.ts index 280e7d0aba..84a0c61661 100644 --- a/src/agents/pi-embedded-runner/compact.ts +++ b/src/agents/pi-embedded-runner/compact.ts @@ -44,6 +44,7 @@ import { createOpenClawCodingTools } from "../pi-tools.js"; import { resolveSandboxContext } from "../sandbox.js"; import { repairSessionFileIfNeeded } from "../session-file-repair.js"; import { guardSessionManager } from "../session-tool-result-guard-wrapper.js"; +import { sanitizeToolUseResultPairing } from "../session-transcript-repair.js"; import { acquireSessionWriteLock } from "../session-write-lock.js"; import { detectRuntimeShell } from "../shell-utils.js"; import { @@ -429,10 +430,16 @@ export async function compactEmbeddedPiSessionDirect( const validated = transcriptPolicy.validateAnthropicTurns ? validateAnthropicTurns(validatedGemini) : validatedGemini; - const limited = limitHistoryTurns( + const truncated = limitHistoryTurns( validated, getDmHistoryLimitFromSessionKey(params.sessionKey, params.config), ); + // Re-run tool_use/tool_result pairing repair after truncation, since + // limitHistoryTurns can orphan tool_result blocks by removing the + // assistant message that contained the matching tool_use. + const limited = transcriptPolicy.repairToolUseResultPairing + ? sanitizeToolUseResultPairing(truncated) + : truncated; if (limited.length > 0) { session.agent.replaceMessages(limited); } diff --git a/src/agents/pi-embedded-runner/run/attempt.ts b/src/agents/pi-embedded-runner/run/attempt.ts index 086b11fae1..893bcbc671 100644 --- a/src/agents/pi-embedded-runner/run/attempt.ts +++ b/src/agents/pi-embedded-runner/run/attempt.ts @@ -48,6 +48,7 @@ import { resolveSandboxContext } from "../../sandbox.js"; import { resolveSandboxRuntimeStatus } from "../../sandbox/runtime-status.js"; import { repairSessionFileIfNeeded } from "../../session-file-repair.js"; import { guardSessionManager } from "../../session-tool-result-guard-wrapper.js"; +import { sanitizeToolUseResultPairing } from "../../session-transcript-repair.js"; import { acquireSessionWriteLock } from "../../session-write-lock.js"; import { detectRuntimeShell } from "../../shell-utils.js"; import { @@ -556,10 +557,16 @@ export async function runEmbeddedAttempt( const validated = transcriptPolicy.validateAnthropicTurns ? validateAnthropicTurns(validatedGemini) : validatedGemini; - const limited = limitHistoryTurns( + const truncated = limitHistoryTurns( validated, getDmHistoryLimitFromSessionKey(params.sessionKey, params.config), ); + // Re-run tool_use/tool_result pairing repair after truncation, since + // limitHistoryTurns can orphan tool_result blocks by removing the + // assistant message that contained the matching tool_use. + const limited = transcriptPolicy.repairToolUseResultPairing + ? sanitizeToolUseResultPairing(truncated) + : truncated; cacheTrace?.recordStage("session:limited", { messages: limited }); if (limited.length > 0) { activeSession.agent.replaceMessages(limited);