From 1dcea837a23740aa24862a0f54669f71db77f2a0 Mon Sep 17 00:00:00 2001 From: jiangnan <1394485448@qq.com> Date: Tue, 3 Mar 2026 03:58:39 +0800 Subject: [PATCH] fix(agents): clear pending tool call state on interruption regardless of provider When `allowSyntheticToolResults` is false (OpenAI, OpenRouter, and most third-party providers), the guard never cleared its pending tool call map when a user message arrived during in-flight tool execution. This left orphaned tool_use blocks in the transcript with no matching tool_result, causing the provider API to reject all subsequent requests with 400 errors and permanently breaking the session. The fix removes the `allowSyntheticToolResults` gate around the flush calls. `flushPendingToolResults()` already handles both cases correctly: it only inserts synthetic results when allowed, and always clears the pending map. The gate was preventing the map from being cleared at all for providers that disable synthetic results. Fixes #32098 Co-Authored-By: Claude Opus 4.6 --- src/agents/session-tool-result-guard.ts | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/agents/session-tool-result-guard.ts b/src/agents/session-tool-result-guard.ts index 5e27a30bd92..31e79eec34f 100644 --- a/src/agents/session-tool-result-guard.ts +++ b/src/agents/session-tool-result-guard.ts @@ -171,7 +171,7 @@ export function installSessionToolResultGuard( allowedToolNames: opts?.allowedToolNames, }); if (sanitized.length === 0) { - if (allowSyntheticToolResults && pending.size > 0) { + if (pending.size > 0) { flushPendingToolResults(); } return undefined; @@ -215,15 +215,18 @@ export function installSessionToolResultGuard( ? extractToolCallsFromAssistant(nextMessage as Extract) : []; - if (allowSyntheticToolResults) { - // If previous tool calls are still pending, flush before non-tool results. - if (pending.size > 0 && (toolCalls.length === 0 || nextRole !== "assistant")) { - flushPendingToolResults(); - } - // If new tool calls arrive while older ones are pending, flush the old ones first. - if (pending.size > 0 && toolCalls.length > 0) { - flushPendingToolResults(); - } + // Always clear pending tool call state before appending non-tool-result messages. + // flushPendingToolResults() only inserts synthetic results when allowSyntheticToolResults + // is true; it always clears the pending map. Without this, providers that disable + // synthetic results (e.g. OpenAI) accumulate stale pending state when a user message + // interrupts in-flight tool calls, leaving orphaned tool_use blocks in the transcript + // that cause API 400 errors on subsequent requests. + if (pending.size > 0 && (toolCalls.length === 0 || nextRole !== "assistant")) { + flushPendingToolResults(); + } + // If new tool calls arrive while older ones are pending, flush the old ones first. + if (pending.size > 0 && toolCalls.length > 0) { + flushPendingToolResults(); } const finalMessage = applyBeforeWriteHook(persistMessage(nextMessage));