diff --git a/src/server/audio-live.ts b/src/server/audio-live.ts index b90921a98e..2f429ace69 100644 --- a/src/server/audio-live.ts +++ b/src/server/audio-live.ts @@ -100,7 +100,7 @@ export async function handleExternalLive( ? frameless ? forwardLiveUrl(relay.providerBaseUrl, false) : keyedLiveUrl(relay.providerBaseUrl) : forwardLiveUrl(relay.providerBaseUrl, true); const upstream = await fetch(url, { method: "POST", headers, body, signal: deadline.signal, redirect: "manual" }); - outcome = upstream.ok ? 502 : upstream.status; + outcome = upstream.status; const detach = cancelBodyOnAbort(upstream.body, deadline.signal); let responseBody: ArrayBuffer | Response; try { responseBody = await readBodyCapped(upstream.body, LIVE_RESPONSE_MAX_BYTES, () => "Live answer too large", deadline.signal); } @@ -122,7 +122,6 @@ export async function handleExternalLive( sidebandBaseUrl: config.experimentalRealtimeWsBaseUrl, }); if (!alias) return formatErrorResponse(503, "server_busy", "Live call could not be registered"); - outcome = upstream.status; return new Response(responseBody, { status: upstream.status, headers: { "content-type": upstream.headers.get("content-type") ?? "application/sdp", location: `/v1/${frameless ? "live" : "realtime/calls"}/${alias}`, diff --git a/src/server/audio-transcriptions.ts b/src/server/audio-transcriptions.ts index 4de98b0b86..c06a93e6d8 100644 --- a/src/server/audio-transcriptions.ts +++ b/src/server/audio-transcriptions.ts @@ -117,7 +117,7 @@ async function transcribeAdmitted( form.append("response_format", "json"); } const upstream = await fetch(url, { method: "POST", headers, body: form, signal: signal.signal, redirect: "manual" }); - outcome = upstream.ok ? 502 : upstream.status; + outcome = upstream.status; const detach = cancelBodyOnAbort(upstream.body, signal.signal); let body: ArrayBuffer | Response; try { @@ -137,7 +137,6 @@ async function transcribeAdmitted( if (!payload || typeof payload !== "object" || !("text" in payload) || typeof payload.text !== "string") { return formatErrorResponse(502, "upstream_error", "Audio upstream response is missing text"); } - outcome = upstream.status; return input.format === "text" ? new Response(payload.text, { headers: { "content-type": "text/plain; charset=utf-8" } }) : Response.json({ text: payload.text }); diff --git a/tests/server/audio-transcriptions.test.ts b/tests/server/audio-transcriptions.test.ts index 98e44e6626..eb6a9cfe75 100644 --- a/tests/server/audio-transcriptions.test.ts +++ b/tests/server/audio-transcriptions.test.ts @@ -327,10 +327,20 @@ describe("standalone transcription API", () => { expect((await captured[0]!.formData()).get("model")).toBeNull(); }); - test("malformed Pool response records one failure and no provisional success", async () => { + test("malformed Pool response records upstream status before body validation (#4502)", async () => { savePoolConfig(); respond = () => Response.json({ missing: "text" }); const outcomes = spyOn(routing, "recordCodexUpstreamOutcome"); + try { + expect((await request()).status).toBe(502); + expect(outcomes.mock.calls.filter(call => call[1] === "pool-a").map(call => call[2])).toEqual([200]); + } finally { outcomes.mockRestore(); } + }); + + test("upstream HTTP error records real failure status for Pool account", async () => { + savePoolConfig(); + respond = () => new Response("upstream failure", { status: 502 }); + const outcomes = spyOn(routing, "recordCodexUpstreamOutcome"); try { expect((await request()).status).toBe(502); expect(outcomes.mock.calls.filter(call => call[1] === "pool-a").map(call => call[2])).toEqual([502]);