From 15eddc85907fa5c2525b89fcb6178c8d97f3cb53 Mon Sep 17 00:00:00 2001 From: Andrew Date: Thu, 10 Sep 2026 15:42:51 -0700 Subject: [PATCH] fix(opencode): classify OpenRouter's typeless 5xx chunks as retryable APIError OpenRouter relays upstream failures (provider unavailable, upstream connect errors, idle timeouts) mid-stream as { code: 5xx, message } with no type field, so parseStreamError returned undefined and the turn died as a non-retryable UnknownError. Treat any typeless numeric 5xx code as a retryable provider error so the session's retry loop handles it. Co-Authored-By: Claude Fable 5.1 --- packages/opencode/src/provider/error.ts | 10 +++++++++- .../opencode/test/session/message-v2.test.ts | 18 ++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/provider/error.ts b/packages/opencode/src/provider/error.ts index b13a3375a483..52d6f08ee62e 100644 --- a/packages/opencode/src/provider/error.ts +++ b/packages/opencode/src/provider/error.ts @@ -99,7 +99,7 @@ export type ParsedStreamError = responseBody: string } -// OpenRouter relays upstream overflow as a typeless `{ code: 502, message }` chunk. +// OpenRouter relays upstream failures as typeless `{ code: 5xx, message }` chunks. function isOverflow(body: { error?: { code?: string; message?: string }; message?: string }) { return body.error?.code === "context_length_exceeded" || isContextOverflow(body.error?.message ?? body.message ?? "") } @@ -117,6 +117,14 @@ export function parseStreamError(input: unknown): ParsedStreamError | undefined responseBody, } } + if (typeof body.code === "number" && body.code >= 500) { + return { + type: "api_error", + message: typeof body.message === "string" ? body.message : "Server error.", + isRetryable: true, + responseBody, + } + } if (body.type !== "error") return switch (body?.error?.code) { diff --git a/packages/opencode/test/session/message-v2.test.ts b/packages/opencode/test/session/message-v2.test.ts index 4dbddcc09b43..cbc89d254b3f 100644 --- a/packages/opencode/test/session/message-v2.test.ts +++ b/packages/opencode/test/session/message-v2.test.ts @@ -1399,6 +1399,24 @@ describe("session.message-v2.fromError", () => { }) }) + test("serializes OpenRouter typeless 5xx chunks as retryable APIError", () => { + const input = { + code: 502, + message: "Stream ended before a terminal response event", + metadata: { error_type: "provider_unavailable" }, + } + const result = MessageV2.fromError(input, { providerID }) + + expect(result).toStrictEqual({ + name: "APIError", + data: { + message: input.message, + isRetryable: true, + responseBody: JSON.stringify(input), + }, + }) + }) + test("serializes response error codes", () => { const cases = [ {