From c164289df09d2d0e2350e7fb5fa3d09708cf1c9d Mon Sep 17 00:00:00 2001 From: nickyleach <331803+nickyleach@users.noreply.github.com> Date: Wed, 16 Sep 2026 11:03:59 +0000 Subject: [PATCH] fix(adapters): compare a caller header name case-insensitively in the API helper The helper set a default Content-Type or X-Paperclip-Run-Id header only when the exact-case name was absent, so a caller-supplied lowercase header did not stop the default and the two values reached the server joined by a comma. Reuse the existing case-insensitive header check for both defaults. Co-authored-by: Paperclip --- .../src/paperclip-api-launcher.test.ts | 22 +++++++++++++++++++ .../src/paperclip-api-launcher.ts | 4 ++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/packages/adapter-utils/src/paperclip-api-launcher.test.ts b/packages/adapter-utils/src/paperclip-api-launcher.test.ts index c68664fef3a..8c3b8980ca7 100644 --- a/packages/adapter-utils/src/paperclip-api-launcher.test.ts +++ b/packages/adapter-utils/src/paperclip-api-launcher.test.ts @@ -96,6 +96,28 @@ describe("paperclip-api helper", () => { expect(request.body).toBe('{"body":"status"}'); }); + it("keeps a caller's lowercase content-type header as the only Content-Type header sent", async () => { + const result = await exec( + bin, + ["POST", "/api/issues/PAP-1/comments", "-H", "content-type: text/plain", "-d", "hello"], + { env: runEnv() }, + ); + expect(JSON.parse(result.stdout)).toEqual({ ok: true }); + const request = configured.requests[0]!; + expect(request.headers["content-type"]).toBe("text/plain"); + }); + + it("keeps a caller's lowercase x-paperclip-run-id header as the only X-Paperclip-Run-Id header sent", async () => { + const result = await exec( + bin, + ["POST", "/api/issues/PAP-1/comments", "-H", "x-paperclip-run-id: caller-run-id", "-d", '{"body":"status"}'], + { env: runEnv() }, + ); + expect(JSON.parse(result.stdout)).toEqual({ ok: true }); + const request = configured.requests[0]!; + expect(request.headers["x-paperclip-run-id"]).toBe("caller-run-id"); + }); + it("never puts the bearer on the command line", async () => { const args = ["POST", "/api/issues/PAP-1/comments", "-d", '{"body":"status"}']; expect(args.join(" ")).not.toContain(SENTINEL_BEARER); diff --git a/packages/adapter-utils/src/paperclip-api-launcher.ts b/packages/adapter-utils/src/paperclip-api-launcher.ts index c9c265e1a6d..dec38a7c843 100644 --- a/packages/adapter-utils/src/paperclip-api-launcher.ts +++ b/packages/adapter-utils/src/paperclip-api-launcher.ts @@ -218,9 +218,9 @@ async function main() { headers['Content-Type'] = multipart.contentType; } else if (parsed.bodySpec !== undefined) { body = await resolveBodySpec(parsed.bodySpec); - if (!('Content-Type' in headers)) headers['Content-Type'] = 'application/json'; + if (!hasHeaderNamed(headers, 'content-type')) headers['Content-Type'] = 'application/json'; } - if (process.env.PAPERCLIP_RUN_ID && !('X-Paperclip-Run-Id' in headers)) headers['X-Paperclip-Run-Id'] = process.env.PAPERCLIP_RUN_ID; + if (process.env.PAPERCLIP_RUN_ID && !hasHeaderNamed(headers, 'x-paperclip-run-id')) headers['X-Paperclip-Run-Id'] = process.env.PAPERCLIP_RUN_ID; // Set once, here. Nothing above this line can add or change this header. headers['Authorization'] = 'Bearer ' + bearer;