diff --git a/src/agents/pi-embedded-runner.sanitize-session-history.test.ts b/src/agents/pi-embedded-runner.sanitize-session-history.test.ts index b428c3328..99532d7f4 100644 --- a/src/agents/pi-embedded-runner.sanitize-session-history.test.ts +++ b/src/agents/pi-embedded-runner.sanitize-session-history.test.ts @@ -94,7 +94,7 @@ describe("sanitizeSessionHistory", () => { ); }); - it("does not sanitize tool call ids for openai-responses", async () => { + it("sanitizes tool call ids for openai-responses to enforce 40-char limit", async () => { vi.mocked(helpers.isGoogleModelApi).mockReturnValue(false); await sanitizeSessionHistory({ @@ -108,7 +108,11 @@ describe("sanitizeSessionHistory", () => { expect(helpers.sanitizeSessionMessagesImages).toHaveBeenCalledWith( mockMessages, "session:history", - expect.objectContaining({ sanitizeMode: "images-only", sanitizeToolCallIds: false }), + expect.objectContaining({ + sanitizeMode: "images-only", + sanitizeToolCallIds: true, + toolCallIdMode: "strict", + }), ); }); diff --git a/src/agents/tool-call-id.test.ts b/src/agents/tool-call-id.test.ts index 5ce554e42..1c412289a 100644 --- a/src/agents/tool-call-id.test.ts +++ b/src/agents/tool-call-id.test.ts @@ -137,6 +137,39 @@ describe("sanitizeToolCallIdsForCloudCodeAssist", () => { expect(r1.toolCallId).toBe(a.id); expect(r2.toolCallId).toBe(b.id); }); + + it("enforces 40-char limit for very long IDs (issue #4718)", () => { + // Simulate a very long tool call ID (like 438 chars reported in issue) + const veryLongId = `call_${"x".repeat(434)}`; + expect(veryLongId.length).toBe(439); // Verify we're testing a long ID + + const input = [ + { + role: "assistant", + content: [{ type: "toolCall", id: veryLongId, name: "gitlab", arguments: {} }], + }, + { + role: "toolResult", + toolCallId: veryLongId, + toolName: "gitlab", + content: [{ type: "text", text: "ok" }], + }, + ] satisfies AgentMessage[]; + + const out = sanitizeToolCallIdsForCloudCodeAssist(input); + expect(out).not.toBe(input); + + const assistant = out[0] as Extract; + const toolCall = assistant.content?.[0] as { id?: string }; + expect(toolCall.id).toBeDefined(); + expect(toolCall.id?.length).toBeLessThanOrEqual(40); + expect(toolCall.id?.length).toBeGreaterThan(0); + expect(isValidCloudCodeAssistToolId(toolCall.id as string, "strict")).toBe(true); + + const result = out[1] as Extract; + expect(result.toolCallId).toBe(toolCall.id); + expect(result.toolCallId?.length).toBeLessThanOrEqual(40); + }); }); describe("strict mode (alphanumeric only)", () => { diff --git a/src/agents/transcript-policy.ts b/src/agents/transcript-policy.ts index 9ae14d38f..cba6467e8 100644 --- a/src/agents/transcript-policy.ts +++ b/src/agents/transcript-policy.ts @@ -85,7 +85,8 @@ export function resolveTranscriptPolicy(params: { const needsNonImageSanitize = isGoogle || isAnthropic || isMistral || isOpenRouterGemini; - const sanitizeToolCallIds = isGoogle || isMistral; + // OpenAI requires tool call IDs <= 40 chars (issue #4718) + const sanitizeToolCallIds = isGoogle || isMistral || isOpenAi; const toolCallIdMode: ToolCallIdMode | undefined = isMistral ? "strict9" : sanitizeToolCallIds @@ -99,7 +100,7 @@ export function resolveTranscriptPolicy(params: { return { sanitizeMode: isOpenAi ? "images-only" : needsNonImageSanitize ? "full" : "images-only", - sanitizeToolCallIds: !isOpenAi && sanitizeToolCallIds, + sanitizeToolCallIds, toolCallIdMode, repairToolUseResultPairing: !isOpenAi && repairToolUseResultPairing, preserveSignatures: isAntigravityClaudeModel,