Skip to content

Commit 2b1224a

Browse files
committed
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.
1 parent bb31b04 commit 2b1224a

File tree

1 file changed

+8
-6
lines changed

1 file changed

+8
-6
lines changed

src/core/assistant-message/presentAssistantMessage.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -280,10 +280,12 @@ export async function presentAssistantMessage(cline: Task) {
280280
// Track if we've already pushed a tool result for this tool call (native protocol only)
281281
let hasToolResult = false
282282

283-
// Check if we're using native tool protocol (do this once before defining pushToolResult)
284-
const toolProtocol = resolveToolProtocol(cline.apiConfiguration, cline.api.getModel().info)
285-
const isNative = isNativeProtocol(toolProtocol)
283+
// Determine protocol by checking if this tool call has an ID.
284+
// Native protocol tool calls ALWAYS have an ID (set when parsed from tool_call chunks).
285+
// XML protocol tool calls NEVER have an ID (parsed from XML text).
286+
// This is the definitive indicator and prevents race conditions from re-resolving the protocol.
286287
const toolCallId = (block as any).id
288+
const isNative = !!toolCallId
287289

288290
const pushToolResult = (content: ToolResponse) => {
289291
if (isNative && toolCallId) {
@@ -511,9 +513,9 @@ export async function presentAssistantMessage(cline: Task) {
511513
case "apply_diff": {
512514
await checkpointSaveAndMark(cline)
513515

514-
// Check if native protocol is enabled - if so, always use single-file class-based tool
515-
const applyDiffToolProtocol = resolveToolProtocol(cline.apiConfiguration, cline.api.getModel().info)
516-
if (isNativeProtocol(applyDiffToolProtocol)) {
516+
// Check if this tool call came from native protocol by checking for ID
517+
// Native calls always have IDs, XML calls never do
518+
if (isNative) {
517519
await applyDiffToolClass.handle(cline, block as ToolUse<"apply_diff">, {
518520
askApproval,
519521
handleError,

0 commit comments

Comments
 (0)