From ae3860b2110fa3ce37b8fc375a7bb25fe8de2d5d Mon Sep 17 00:00:00 2001 From: Wendell Misiedjan Date: Mon, 4 May 2026 18:03:21 +0200 Subject: [PATCH] fix: retry codex overloaded stream errors --- packages/opencode/src/provider/error.ts | 1 + packages/opencode/src/session/retry.ts | 15 ++++++-- packages/opencode/test/session/retry.test.ts | 39 ++++++++++++++++++++ 3 files changed, 51 insertions(+), 4 deletions(-) diff --git a/packages/opencode/src/provider/error.ts b/packages/opencode/src/provider/error.ts index 3877dcb7f304..796294797493 100644 --- a/packages/opencode/src/provider/error.ts +++ b/packages/opencode/src/provider/error.ts @@ -152,6 +152,7 @@ export function parseStreamError(input: unknown): ParsedStreamError | undefined responseBody, } case "server_error": + case "server_is_overloaded": return { type: "api_error", message: typeof body?.error?.message === "string" ? body?.error?.message : "Server error.", diff --git a/packages/opencode/src/session/retry.ts b/packages/opencode/src/session/retry.ts index e81e1973751f..1b7a03c3a4c6 100644 --- a/packages/opencode/src/session/retry.ts +++ b/packages/opencode/src/session/retry.ts @@ -60,7 +60,7 @@ export function retryable(error: Err) { // even when the provider SDK doesn't explicitly mark them as retryable. if (!error.data.isRetryable && !(status !== undefined && status >= 500)) return undefined if (error.data.responseBody?.includes("FreeUsageLimitError")) return GO_UPSELL_MESSAGE - return error.data.message.includes("Overloaded") ? "Provider is overloaded" : error.data.message + return error.data.message.toLowerCase().includes("overloaded") ? "Provider is overloaded" : error.data.message } // Check for rate limit patterns in plain text error messages @@ -89,15 +89,22 @@ export function retryable(error: Err) { } }) if (!json || typeof json !== "object") return undefined - const code = typeof json.code === "string" ? json.code : "" + const codes = [ + typeof json.code === "string" ? json.code : "", + typeof json.error?.code === "string" ? json.error.code : "", + typeof json.error?.type === "string" ? json.error.type : "", + ] if (json.type === "error" && json.error?.type === "too_many_requests") { return "Too Many Requests" } - if (code.includes("exhausted") || code.includes("unavailable")) { + if (codes.some((code) => code.includes("exhausted") || code.includes("unavailable") || code.includes("overloaded"))) { return "Provider is overloaded" } - if (json.type === "error" && typeof json.error?.code === "string" && json.error.code.includes("rate_limit")) { + if ( + json.type === "error" && + codes.some((code) => code.includes("rate_limit") || code.includes("too_many_requests")) + ) { return "Rate Limited" } return undefined diff --git a/packages/opencode/test/session/retry.test.ts b/packages/opencode/test/session/retry.test.ts index 105c772d9735..d0de7903c5af 100644 --- a/packages/opencode/test/session/retry.test.ts +++ b/packages/opencode/test/session/retry.test.ts @@ -126,6 +126,22 @@ describe("session.retry.retryable", () => { expect(SessionRetry.retryable(error)).toBe("Provider is overloaded") }) + test("maps nested overloaded provider errors", () => { + const error = wrap( + JSON.stringify({ + type: "error", + sequence_number: 2, + error: { + type: "service_unavailable_error", + code: "server_is_overloaded", + message: "Our servers are currently overloaded. Please try again later.", + param: null, + }, + }), + ) + expect(SessionRetry.retryable(error)).toBe("Provider is overloaded") + }) + test("does not retry unknown json messages", () => { const error = wrap(JSON.stringify({ error: { message: "no_kv_space" } })) expect(SessionRetry.retryable(error)).toBeUndefined() @@ -323,4 +339,27 @@ describe("session.message-v2.fromError", () => { expect(result.data.isRetryable).toBe(true) expect(SessionRetry.retryable(result)).toBe("An error occurred while processing your request.") }) + + test("converts OpenAI server_is_overloaded stream chunks to retryable APIError", () => { + const result = MessageV2.fromError( + { + message: JSON.stringify({ + type: "error", + sequence_number: 2, + error: { + type: "service_unavailable_error", + code: "server_is_overloaded", + message: "Our servers are currently overloaded. Please try again later.", + param: null, + }, + }), + }, + { providerID: ProviderID.make("openai") }, + ) + + expect(MessageV2.APIError.isInstance(result)).toBe(true) + if (!MessageV2.APIError.isInstance(result)) throw new Error("expected APIError") + expect(result.data.isRetryable).toBe(true) + expect(SessionRetry.retryable(result)).toBe("Provider is overloaded") + }) })