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 <noreply@anthropic.com>
This commit is contained in:
jiangnan
2026-03-03 03:58:39 +08:00
committed by Peter Steinberger
parent 21d6d878ce
commit 1dcea837a2

View File

@@ -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<AgentMessage, { role: "assistant" }>)
: [];
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));