From 533fe3c2d9b2fb5d97a73294a54db4481587fbdc Mon Sep 17 00:00:00 2001 From: Vadym O Date: Sun, 18 Oct 2026 22:50:15 +0800 Subject: [PATCH] fix(combo): retry zero-output response.incomplete with no reason A combo target that ends with `response.incomplete` before any output and without `incomplete_details.reason` was accepted as the turn's answer, so the caller got an empty incomplete turn instead of failover to the next target. Treat a missing reason like the transport reasons already listed; semantic reasons such as max_output_tokens still commit the target. Co-Authored-By: Claude Code --- src/server/responses/combo-stream-preflight.ts | 3 ++- tests/routing/combo-stream-preflight.test.ts | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/server/responses/combo-stream-preflight.ts b/src/server/responses/combo-stream-preflight.ts index 5cdc1b4ddb6..e6a95e518ba 100644 --- a/src/server/responses/combo-stream-preflight.ts +++ b/src/server/responses/combo-stream-preflight.ts @@ -121,7 +121,8 @@ function retryableZeroOutputTerminal(payload: unknown): boolean { if (event.type === "response.failed") return true; if (event.type !== "response.incomplete") return false; const reason = event.response?.incomplete_details?.reason; - return typeof reason === "string" && RETRYABLE_ZERO_OUTPUT_INCOMPLETE_REASONS.has(reason); + return reason === undefined + || (typeof reason === "string" && RETRYABLE_ZERO_OUTPUT_INCOMPLETE_REASONS.has(reason)); } /** diff --git a/tests/routing/combo-stream-preflight.test.ts b/tests/routing/combo-stream-preflight.test.ts index 29e6d34c3e7..a4487a4b7c3 100644 --- a/tests/routing/combo-stream-preflight.test.ts +++ b/tests/routing/combo-stream-preflight.test.ts @@ -116,6 +116,20 @@ describe("combo stream preflight", () => { } }); + test("converts a zero-output incomplete without a stated reason into a retryable failure", async () => { + for (const response of [ + { id: "r1", status: "incomplete" }, + { id: "r1", status: "incomplete", incomplete_details: null }, + ]) { + const result = await preflightComboStreamResponse(sse( + { type: "response.created", response: { id: "r1", status: "in_progress" } }, + { type: "response.incomplete", response }, + ), { model: "m1", provider: "a" }); + expect(result.kind).toBe("failed"); + expect(result.response.status).toBe(502); + } + }); + test("does not replay semantic incompletes that another provider cannot safely repair", async () => { const source = sse( { type: "response.created", response: { id: "r1", status: "in_progress" } },