From 5355b476f67a6ccd17758ca41695b7c6648c2297 Mon Sep 17 00:00:00 2001 From: Aswin Sreenivas Date: Thu, 29 Jan 2026 22:20:28 +0800 Subject: [PATCH] fix(agent): sanitize messages after orphan user repair When repairing orphaned trailing user messages, buildSessionContext() was called but messages bypassed the sanitization pipeline. This could cause tool_result blocks without matching tool_use blocks, leading to Anthropic API "unexpected tool_use_id" errors. Apply full sanitization (sanitizeSessionHistory, validateTurns, limitHistoryTurns) to rebuilt messages. --- src/agents/pi-embedded-runner/run/attempt.ts | 23 +++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/agents/pi-embedded-runner/run/attempt.ts b/src/agents/pi-embedded-runner/run/attempt.ts index 776525658..2c496eb50 100644 --- a/src/agents/pi-embedded-runner/run/attempt.ts +++ b/src/agents/pi-embedded-runner/run/attempt.ts @@ -709,7 +709,28 @@ export async function runEmbeddedAttempt( sessionManager.resetLeaf(); } const sessionContext = sessionManager.buildSessionContext(); - activeSession.agent.replaceMessages(sessionContext.messages); + // Re-sanitize the rebuilt messages to ensure tool_use/tool_result pairing is valid. + // Without this, Anthropic API rejects the request with "unexpected tool_use_id" errors. + const rebuiltSanitized = await sanitizeSessionHistory({ + messages: sessionContext.messages, + modelApi: params.model.api, + modelId: params.modelId, + provider: params.provider, + sessionManager, + sessionId: params.sessionId, + policy: transcriptPolicy, + }); + const rebuiltValidatedGemini = transcriptPolicy.validateGeminiTurns + ? validateGeminiTurns(rebuiltSanitized) + : rebuiltSanitized; + const rebuiltValidated = transcriptPolicy.validateAnthropicTurns + ? validateAnthropicTurns(rebuiltValidatedGemini) + : rebuiltValidatedGemini; + const rebuiltLimited = limitHistoryTurns( + rebuiltValidated, + getDmHistoryLimitFromSessionKey(params.sessionKey, params.config), + ); + activeSession.agent.replaceMessages(rebuiltLimited); log.warn( `Removed orphaned user message to prevent consecutive user turns. ` + `runId=${params.runId} sessionId=${params.sessionId}`,