From 2b1224a347cd0d23170fa72491a15b692d0810bc Mon Sep 17 00:00:00 2001 From: daniel-lxs Date: Tue, 18 Nov 2025 13:28:10 -0500 Subject: [PATCH 1/2] fix: resolve native tool protocol race condition causing 400 errors The issue occurred when tool results were being formatted. The code was calling resolveToolProtocol() to determine whether to use native or XML protocol formatting. However, if the API configuration changed between when the tool call was made and when the result was being formatted (e.g., user switched profiles), it would use the wrong protocol format. This caused native protocol tool calls to be formatted with XML-style text headers like '[tool_name] Result:', creating malformed message structures that violated API expectations and resulted in 400 errors. Solution: Instead of recalculating the protocol, we now check for the presence of an ID on the tool call itself: - Native protocol tool calls ALWAYS have an ID (set during parsing) - XML protocol tool calls NEVER have an ID (parsed from XML text) This approach is race-condition-free because the ID is an immutable property of each tool call that definitively indicates which protocol was used to create it. It also gracefully handles provider switches - if a provider doesn't support native tools, the tool calls simply won't have IDs and will be formatted correctly for XML protocol. --- .../assistant-message/presentAssistantMessage.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/core/assistant-message/presentAssistantMessage.ts b/src/core/assistant-message/presentAssistantMessage.ts index 9895c100704..455dd359661 100644 --- a/src/core/assistant-message/presentAssistantMessage.ts +++ b/src/core/assistant-message/presentAssistantMessage.ts @@ -280,10 +280,12 @@ export async function presentAssistantMessage(cline: Task) { // Track if we've already pushed a tool result for this tool call (native protocol only) let hasToolResult = false - // Check if we're using native tool protocol (do this once before defining pushToolResult) - const toolProtocol = resolveToolProtocol(cline.apiConfiguration, cline.api.getModel().info) - const isNative = isNativeProtocol(toolProtocol) + // Determine protocol by checking if this tool call has an ID. + // Native protocol tool calls ALWAYS have an ID (set when parsed from tool_call chunks). + // XML protocol tool calls NEVER have an ID (parsed from XML text). + // This is the definitive indicator and prevents race conditions from re-resolving the protocol. const toolCallId = (block as any).id + const isNative = !!toolCallId const pushToolResult = (content: ToolResponse) => { if (isNative && toolCallId) { @@ -511,9 +513,9 @@ export async function presentAssistantMessage(cline: Task) { case "apply_diff": { await checkpointSaveAndMark(cline) - // Check if native protocol is enabled - if so, always use single-file class-based tool - const applyDiffToolProtocol = resolveToolProtocol(cline.apiConfiguration, cline.api.getModel().info) - if (isNativeProtocol(applyDiffToolProtocol)) { + // Check if this tool call came from native protocol by checking for ID + // Native calls always have IDs, XML calls never do + if (isNative) { await applyDiffToolClass.handle(cline, block as ToolUse<"apply_diff">, { askApproval, handleError, From f30d4138c6f374771fac452f350f6c93e71a6151 Mon Sep 17 00:00:00 2001 From: daniel-lxs Date: Tue, 18 Nov 2025 13:33:01 -0500 Subject: [PATCH 2/2] fix: remove redundant comment to clarify native tool protocol handling --- src/core/assistant-message/presentAssistantMessage.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/core/assistant-message/presentAssistantMessage.ts b/src/core/assistant-message/presentAssistantMessage.ts index 455dd359661..d4389c22e89 100644 --- a/src/core/assistant-message/presentAssistantMessage.ts +++ b/src/core/assistant-message/presentAssistantMessage.ts @@ -283,7 +283,6 @@ export async function presentAssistantMessage(cline: Task) { // Determine protocol by checking if this tool call has an ID. // Native protocol tool calls ALWAYS have an ID (set when parsed from tool_call chunks). // XML protocol tool calls NEVER have an ID (parsed from XML text). - // This is the definitive indicator and prevents race conditions from re-resolving the protocol. const toolCallId = (block as any).id const isNative = !!toolCallId