From ae73b90280b5c53a43ef866517f0dc220c29cb84 Mon Sep 17 00:00:00 2001 From: Hernan Javier Ardila Sanchez Date: Thu, 21 May 2026 23:08:54 +0200 Subject: [PATCH] fix(handler): always normalize system role messages in claude passthrough paths (#2468) (#2519) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix(handler): always normalize system role messages in claude passthrough paths — integrated into release/v3.8.2 --- open-sse/handlers/chatCore.ts | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/open-sse/handlers/chatCore.ts b/open-sse/handlers/chatCore.ts index 649c75efa4..da9dc7f94f 100644 --- a/open-sse/handlers/chatCore.ts +++ b/open-sse/handlers/chatCore.ts @@ -2779,6 +2779,12 @@ export async function handleChatCore({ preserveClaudeMessages: sourceFormat === FORMATS.CLAUDE && isClaudeCodeSemanticPassthrough, }); log?.debug?.("FORMAT", "claude-code-compatible bridge enabled"); + + // Fix #2468: extract role:"system" from messages → top-level system + // in the compatible bridge path. The preserveClaudeMessages flag + // skips the OpenAI round-trip but may leave system-role messages in + // the array, which Anthropic's Messages API now rejects (400). + normalizeClaudeUpstreamMessages(translatedBody, { preserveToolResultBlocks: true }); } else if (isClaudePassthrough) { // Pure passthrough: forward the body as-is without OpenAI round-trip. // The Claude→OpenAI→Claude double translation was lossy and corrupted @@ -2798,14 +2804,12 @@ export async function handleChatCore({ ) as typeof translatedBody.messages; } - if (!isClaudeCodeSemanticPassthrough) { - normalizeClaudeUpstreamMessages(translatedBody, { preserveToolResultBlocks: true }); - } else { - // Semantic passthrough preserves messages as-is, but system/developer - // role messages must still be moved to the top-level system parameter. - extractSystemMessagesToBody(translatedBody); - log?.debug?.("FORMAT", "claude-code semantic passthrough enabled"); - } + // Fix #2468: always extract role:"system" → top-level system. + // The semantic passthrough correctly skips the Claude→OpenAI→Claude + // round-trip, but even pure Claude bodies may carry system content as + // role:"system" messages rather than the top-level system field, which + // Anthropic's Messages API now rejects with a 400. + normalizeClaudeUpstreamMessages(translatedBody, { preserveToolResultBlocks: true }); log?.debug?.("FORMAT", `claude passthrough (preserveCache=${preserveCacheControl})`);