From e5a5cc6f76670b94fd0b2604195a0bfd213b5c23 Mon Sep 17 00:00:00 2001 From: luvs01 Date: Mon, 7 Sep 2026 15:24:05 +0900 Subject: [PATCH] fix(server): admit exported chat clients on loopback listener --- src/server/index.ts | 5 +++++ tests/server/loopback-listener-integration.test.ts | 11 ++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/server/index.ts b/src/server/index.ts index 8d46c30ab6..dd4a684288 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -813,6 +813,10 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server { { method: "GET", path: "/" }, { method: "GET", path: "/healthz" }, { method: "GET", path: "/readyz" }, - { method: "POST", path: "/v1/chat/completions", body: '{"model":"x","messages":[]}' }, { method: "POST", path: "/v1/messages", body: '{"model":"x","messages":[]}' }, { method: "GET", path: "/v1/opencodex/artifacts/x" }, // Voice call-create is admitted only as POST; the keyed sideband join only as an upgrade. @@ -316,6 +315,16 @@ describe("unauthenticated loopback listener", () => { // And an allowlisted route is genuinely reachable, so the rejections above are not // passing merely because nothing works on this listener. expect((await fetch(`${base}/v1/models`)).status).toBe(200); + + // Exported Raycast and other OpenAI-compatible clients target this listener and append + // /chat/completions to its /v1 base URL. The request must reach the normal handler rather + // than being rejected by the listener gate; the synthetic model can then fail normally. + const chatResponse = await fetch(`${base}/v1/chat/completions`, { + method: "POST", + body: '{"model":"x","messages":[]}', + headers: { "content-type": "application/json" }, + }); + expect(chatResponse.status).not.toBe(404); } finally { await server.stop(true); }