From 0f3a0f78bc389c021701c5fd5c92bfbdd4aa710d Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Sat, 17 Jan 2026 20:20:39 +0100 Subject: [PATCH] fix(ai): prevent orphaned tool results after errored assistant messages When an assistant message has stopReason 'error', its tool calls are now excluded from pending tool tracking. This prevents synthetic tool results from being generated for calls that will be dropped by provider-specific converters (e.g., Codex drops tool calls from errored messages). Previously, this mismatch caused OpenAI to reject requests with 'No tool call found for function call output with call_id ...' errors. fixes #812 --- packages/ai/CHANGELOG.md | 4 ++++ packages/ai/src/providers/transform-messages.ts | 7 ++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index 1b292060..cf4eb8a2 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Fixed orphaned tool results after errored assistant messages causing Codex API errors. When an assistant message has `stopReason: "error"`, its tool calls are now excluded from pending tool tracking, preventing synthetic tool results from being generated for calls that will be dropped by provider-specific converters. ([#812](https://github.com/badlogic/pi-mono/issues/812)) + ## [0.48.0] - 2026-01-16 ### Fixed diff --git a/packages/ai/src/providers/transform-messages.ts b/packages/ai/src/providers/transform-messages.ts index 14b33545..08c4cffa 100644 --- a/packages/ai/src/providers/transform-messages.ts +++ b/packages/ai/src/providers/transform-messages.ts @@ -111,8 +111,13 @@ export function transformMessages(messages: Message[], model: } // Track tool calls from this assistant message + // Don't track tool calls from errored messages - they will be dropped by + // provider-specific converters, so we shouldn't create synthetic results for them const assistantMsg = msg as AssistantMessage; - const toolCalls = assistantMsg.content.filter((b) => b.type === "toolCall") as ToolCall[]; + const toolCalls = + assistantMsg.stopReason === "error" + ? [] + : (assistantMsg.content.filter((b) => b.type === "toolCall") as ToolCall[]); if (toolCalls.length > 0) { pendingToolCalls = toolCalls; existingToolResultIds = new Set();