Skip to content
This repository was archived by the owner on May 15, 2026. It is now read-only.

Commit fc71291

Browse files
daniel-lxshannesrudolph
authored andcommitted
fix: sanitize tool_use_id in tool_result blocks to match API history (#11131)
Tool IDs from providers like Gemini/OpenRouter contain special characters (e.g., 'functions.read_file:0') that are sanitized when saving tool_use blocks to API history. However, tool_result blocks were using the original unsanitized IDs, causing ToolResultIdMismatchError. This fix ensures tool_result blocks use sanitizeToolUseId() to match the sanitized tool_use IDs in conversation history. Fixes EXT-711
1 parent 9d2907f commit fc71291

2 files changed

Lines changed: 16 additions & 7 deletions

File tree

src/core/assistant-message/presentAssistantMessage.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import { isValidToolName, validateToolUse } from "../tools/validateToolUse"
4040
import { codebaseSearchTool } from "../tools/CodebaseSearchTool"
4141

4242
import { formatResponse } from "../prompts/responses"
43+
import { sanitizeToolUseId } from "../../utils/tool-id"
4344

4445
/**
4546
* Processes and presents assistant message content to the user interface.
@@ -118,7 +119,7 @@ export async function presentAssistantMessage(cline: Task) {
118119
if (toolCallId) {
119120
cline.pushToolResultToUserContent({
120121
type: "tool_result",
121-
tool_use_id: toolCallId,
122+
tool_use_id: sanitizeToolUseId(toolCallId),
122123
content: errorMessage,
123124
is_error: true,
124125
})
@@ -169,7 +170,7 @@ export async function presentAssistantMessage(cline: Task) {
169170
if (toolCallId) {
170171
cline.pushToolResultToUserContent({
171172
type: "tool_result",
172-
tool_use_id: toolCallId,
173+
tool_use_id: sanitizeToolUseId(toolCallId),
173174
content: resultContent,
174175
})
175176

@@ -399,7 +400,7 @@ export async function presentAssistantMessage(cline: Task) {
399400

400401
cline.pushToolResultToUserContent({
401402
type: "tool_result",
402-
tool_use_id: toolCallId,
403+
tool_use_id: sanitizeToolUseId(toolCallId),
403404
content: errorMessage,
404405
is_error: true,
405406
})
@@ -436,7 +437,7 @@ export async function presentAssistantMessage(cline: Task) {
436437
// continue gracefully.
437438
cline.pushToolResultToUserContent({
438439
type: "tool_result",
439-
tool_use_id: toolCallId,
440+
tool_use_id: sanitizeToolUseId(toolCallId),
440441
content: formatResponse.toolError(errorMessage),
441442
is_error: true,
442443
})
@@ -482,7 +483,7 @@ export async function presentAssistantMessage(cline: Task) {
482483

483484
cline.pushToolResultToUserContent({
484485
type: "tool_result",
485-
tool_use_id: toolCallId,
486+
tool_use_id: sanitizeToolUseId(toolCallId),
486487
content: resultContent,
487488
})
488489

@@ -644,7 +645,7 @@ export async function presentAssistantMessage(cline: Task) {
644645
// Push tool_result directly without setting didAlreadyUseTool
645646
cline.pushToolResultToUserContent({
646647
type: "tool_result",
647-
tool_use_id: toolCallId,
648+
tool_use_id: sanitizeToolUseId(toolCallId),
648649
content: typeof errorContent === "string" ? errorContent : "(validation error)",
649650
is_error: true,
650651
})
@@ -948,7 +949,7 @@ export async function presentAssistantMessage(cline: Task) {
948949
// This prevents the stream from being interrupted with "Response interrupted by tool use result"
949950
cline.pushToolResultToUserContent({
950951
type: "tool_result",
951-
tool_use_id: toolCallId,
952+
tool_use_id: sanitizeToolUseId(toolCallId),
952953
content: formatResponse.toolError(errorMessage),
953954
is_error: true,
954955
})

src/utils/__tests__/tool-id.spec.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,14 @@ describe("sanitizeToolUseId", () => {
4747
it("should replace multiple invalid characters", () => {
4848
expect(sanitizeToolUseId("mcp.server:tool/name")).toBe("mcp_server_tool_name")
4949
})
50+
51+
it("should sanitize Gemini/OpenRouter function call IDs with dots and colons", () => {
52+
// This is the exact pattern seen in PostHog errors where tool_result IDs
53+
// didn't match tool_use IDs due to missing sanitization
54+
expect(sanitizeToolUseId("functions.read_file:0")).toBe("functions_read_file_0")
55+
expect(sanitizeToolUseId("functions.write_to_file:1")).toBe("functions_write_to_file_1")
56+
expect(sanitizeToolUseId("read_file:0")).toBe("read_file_0")
57+
})
5058
})
5159

5260
describe("real-world MCP tool use ID patterns", () => {

0 commit comments

Comments
 (0)