From fa1d1fe7eb4bae77f41d481d8c015e449816bafb Mon Sep 17 00:00:00 2001 From: Mrinal Joshi Date: Fri, 15 May 2026 20:13:43 +0100 Subject: [PATCH] fix(sse): remove dead-code flag leak in claudeCodeToolRemapper remapToolNamesInRequest() set body._claudeCodeRequiresLowercaseToolNames = true when a request contained only lowercase tool names. The flag had no readers in src/ or open-sse/ (verified by repo-wide grep) and leaked into the outgoing Anthropic /v1/messages payload, causing HTTP 400: "_claudeCodeRequiresLowercaseToolNames: Extra inputs are not permitted" The response-side lowercase remap via remapToolNamesInResponse(text, true) is unconditional and does not depend on this flag, so removing it is a no-op for the response path while fixing the request path. Adds tests/unit/claude-code-tool-remapper-flag-leak.test.ts as a regression guard with 5 cases covering all-lowercase, all-TitleCase, mixed-case, and a flag-leak sweep across all input shapes. --- open-sse/services/claudeCodeToolRemapper.ts | 9 ++- ...laude-code-tool-remapper-flag-leak.test.ts | 59 +++++++++++++++++++ 2 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 tests/unit/claude-code-tool-remapper-flag-leak.test.ts diff --git a/open-sse/services/claudeCodeToolRemapper.ts b/open-sse/services/claudeCodeToolRemapper.ts index 034cba4f7e..8e8b457be5 100644 --- a/open-sse/services/claudeCodeToolRemapper.ts +++ b/open-sse/services/claudeCodeToolRemapper.ts @@ -88,9 +88,12 @@ export function remapToolNamesInRequest(body: Record): boolean } } - if (hasLowercase && !hasTitleCase) { - body._claudeCodeRequiresLowercaseToolNames = true; - } + // NOTE: previously set body._claudeCodeRequiresLowercaseToolNames = true here. + // Removed: the flag had no readers in the codebase and leaked into the + // outgoing Anthropic request body, causing HTTP 400 + // "_claudeCodeRequiresLowercaseToolNames: Extra inputs are not permitted". + // The response-side lowercase remap is unconditional anyway via + // remapToolNamesInResponse(text, forceLowercase=true). return hasLowercase && !hasTitleCase; } diff --git a/tests/unit/claude-code-tool-remapper-flag-leak.test.ts b/tests/unit/claude-code-tool-remapper-flag-leak.test.ts new file mode 100644 index 0000000000..e5deb21b0b --- /dev/null +++ b/tests/unit/claude-code-tool-remapper-flag-leak.test.ts @@ -0,0 +1,59 @@ +/** + * Regression test for the _claudeCodeRequiresLowercaseToolNames flag leak + * that caused HTTP 400 "Extra inputs are not permitted" from Anthropic. + * + * The flag had no readers in the codebase but was assigned to the outgoing + * request body. Anthropic's strict schema validation rejected the unknown + * field. This test guards against re-introduction. + */ +import { describe, it } from "node:test"; +import assert from "node:assert/strict"; +import { remapToolNamesInRequest } from "../../open-sse/services/claudeCodeToolRemapper.ts"; + +describe("remapToolNamesInRequest — flag-leak regression", () => { + it("does NOT add _claudeCodeRequiresLowercaseToolNames when all tools are lowercase", () => { + const body: Record = { + tools: [{ name: "bash" }, { name: "read" }, { name: "edit" }], + }; + remapToolNamesInRequest(body); + assert.equal( + "_claudeCodeRequiresLowercaseToolNames" in body, + false, + "Flag must not leak into outgoing request body" + ); + }); + + it("returns true when only lowercase tools are present", () => { + const body: Record = { tools: [{ name: "bash" }] }; + assert.equal(remapToolNamesInRequest(body), true); + }); + + it("returns false when only TitleCase tools are present", () => { + const body: Record = { tools: [{ name: "Bash" }] }; + assert.equal(remapToolNamesInRequest(body), false); + }); + + it("returns false when mixed-case tools are present", () => { + const body: Record = { + tools: [{ name: "bash" }, { name: "Read" }], + }; + assert.equal(remapToolNamesInRequest(body), false); + }); + + it("does NOT add flag in any of the above cases", () => { + for (const tools of [ + [{ name: "bash" }], + [{ name: "Bash" }], + [{ name: "bash" }, { name: "Read" }], + [], + ]) { + const body: Record = { tools }; + remapToolNamesInRequest(body); + assert.equal( + "_claudeCodeRequiresLowercaseToolNames" in body, + false, + `Flag leaked for tools=${JSON.stringify(tools)}` + ); + } + }); +});