From f9ff4603502af0e05af03fc9695eb47cda81010f Mon Sep 17 00:00:00 2001 From: t3-turbo-bot Date: Tue, 18 Aug 2026 16:18:03 -0400 Subject: [PATCH] fix(openrouter): disable Claude Code tool search for OpenRouter sessions Claude Code defaults to tool search, which omits tools from `tools[]` and refers to them with tool_reference blocks. OpenRouter's Anthropic-compatible endpoint rejects that for every non-Anthropic model, so any turn on a model like moonshotai/kimi-k3 died with: 400 Deferred custom tools are only supported on Anthropic models. Claude Code's own auto-disable only fires when the resolved auth kind is firstParty, which it is not once ANTHROPIC_AUTH_TOKEN carries the OpenRouter key, so the guard never triggered here. Set ENABLE_TOOL_SEARCH=false in the OpenRouter process env (Claude Code parses "false" into its off mode). An explicit host value still wins, for proxies that do forward tool_reference. Co-Authored-By: Claude Fable 5 --- .../provider/openrouter/OpenRouterRuntime.test.ts | 12 ++++++++++++ .../src/provider/openrouter/OpenRouterRuntime.ts | 11 +++++++++++ 2 files changed, 23 insertions(+) diff --git a/apps/server/src/provider/openrouter/OpenRouterRuntime.test.ts b/apps/server/src/provider/openrouter/OpenRouterRuntime.test.ts index 3b078abd6e4d..aedbf4688e5c 100644 --- a/apps/server/src/provider/openrouter/OpenRouterRuntime.test.ts +++ b/apps/server/src/provider/openrouter/OpenRouterRuntime.test.ts @@ -63,11 +63,23 @@ describe("OpenRouterRuntime", () => { expect(env.X_TITLE).toBe("T3 Code"); // Claude Code only forwards headers through ANTHROPIC_CUSTOM_HEADERS. expect(env.ANTHROPIC_CUSTOM_HEADERS).toBe("HTTP-Referer: https://t3.chat\nX-Title: T3 Code"); + // OpenRouter rejects deferred tools for non-Anthropic models. + expect(env.ENABLE_TOOL_SEARCH).toBe("false"); expect(env.OR_SITE_URL).toBeUndefined(); expect(env.OR_APP_NAME).toBeUndefined(); expect(env.PATH).toBe("/usr/bin"); }); + it("keeps an explicit host tool-search choice", () => { + const settings = decodeOpenRouterSettings({ apiKey: "sk-or-test" }); + const env = buildOpenRouterProcessEnv(settings, { ENABLE_TOOL_SEARCH: "true" }); + expect(env.ENABLE_TOOL_SEARCH).toBe("true"); + + // A blank host value is not a choice, so the OpenRouter default applies. + const blank = buildOpenRouterProcessEnv(settings, { ENABLE_TOOL_SEARCH: " " }); + expect(blank.ENABLE_TOOL_SEARCH).toBe("false"); + }); + it("clears inherited Anthropic credentials when settings apiKey is empty", () => { const settings = decodeOpenRouterSettings({ apiKey: "", diff --git a/apps/server/src/provider/openrouter/OpenRouterRuntime.ts b/apps/server/src/provider/openrouter/OpenRouterRuntime.ts index bed1f7e556d7..7527c21cdd0a 100644 --- a/apps/server/src/provider/openrouter/OpenRouterRuntime.ts +++ b/apps/server/src/provider/openrouter/OpenRouterRuntime.ts @@ -120,6 +120,17 @@ export function buildOpenRouterProcessEnv( next.ANTHROPIC_CUSTOM_HEADERS = customHeaders.join("\n"); } + // Claude Code defaults to tool search, which omits tools from `tools[]` and + // refers to them with tool_reference blocks. OpenRouter's Anthropic-compatible + // endpoint rejects that for every non-Anthropic model ("Deferred custom tools + // are only supported on Anthropic models"), which kills the turn outright. + // "false" is Claude Code's own off switch (ENABLE_TOOL_SEARCH => "standard" + // mode). An explicit host value still wins, for proxies that do forward + // tool_reference blocks. + if (next.ENABLE_TOOL_SEARCH === undefined || next.ENABLE_TOOL_SEARCH.trim().length === 0) { + next.ENABLE_TOOL_SEARCH = "false"; + } + return next; }