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.
This commit is contained in:
Mrinal Joshi
2026-05-15 20:13:43 +01:00
parent e9904f1394
commit fa1d1fe7eb
2 changed files with 65 additions and 3 deletions

View File

@@ -88,9 +88,12 @@ export function remapToolNamesInRequest(body: Record<string, unknown>): 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;
}

View File

@@ -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<string, unknown> = {
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<string, unknown> = { tools: [{ name: "bash" }] };
assert.equal(remapToolNamesInRequest(body), true);
});
it("returns false when only TitleCase tools are present", () => {
const body: Record<string, unknown> = { tools: [{ name: "Bash" }] };
assert.equal(remapToolNamesInRequest(body), false);
});
it("returns false when mixed-case tools are present", () => {
const body: Record<string, unknown> = {
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<string, unknown> = { tools };
remapToolNamesInRequest(body);
assert.equal(
"_claudeCodeRequiresLowercaseToolNames" in body,
false,
`Flag leaked for tools=${JSON.stringify(tools)}`
);
}
});
});