diff --git a/packages/opencode/src/session/retry.ts b/packages/opencode/src/session/retry.ts index 39eb8cfb74..6aad55f3f8 100644 --- a/packages/opencode/src/session/retry.ts +++ b/packages/opencode/src/session/retry.ts @@ -56,7 +56,10 @@ export namespace SessionRetry { // context overflow errors should not be retried if (MessageV2.ContextOverflowError.isInstance(error)) return undefined if (MessageV2.APIError.isInstance(error)) { - if (!error.data.isRetryable) return undefined + const status = error.data.statusCode + // 5xx errors are transient server failures and should always be retried, + // 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 } diff --git a/packages/opencode/test/session/retry.test.ts b/packages/opencode/test/session/retry.test.ts index 314306ba62..2d01a8f354 100644 --- a/packages/opencode/test/session/retry.test.ts +++ b/packages/opencode/test/session/retry.test.ts @@ -178,6 +178,47 @@ describe("session.retry.retryable", () => { expect(SessionRetry.retryable(error)).toBeUndefined() }) + test("retries 500 errors even when isRetryable is false", () => { + const error = new MessageV2.APIError({ + message: "Internal server error", + isRetryable: false, + statusCode: 500, + responseBody: '{"type":"api_error","message":"Internal server error"}', + }).toObject() as MessageV2.APIError + + expect(SessionRetry.retryable(error)).toBe("Internal server error") + }) + + test("retries 502 bad gateway errors", () => { + const error = new MessageV2.APIError({ + message: "Bad gateway", + isRetryable: false, + statusCode: 502, + }).toObject() as MessageV2.APIError + + expect(SessionRetry.retryable(error)).toBe("Bad gateway") + }) + + test("retries 503 service unavailable errors", () => { + const error = new MessageV2.APIError({ + message: "Service unavailable", + isRetryable: false, + statusCode: 503, + }).toObject() as MessageV2.APIError + + expect(SessionRetry.retryable(error)).toBe("Service unavailable") + }) + + test("does not retry 4xx errors when isRetryable is false", () => { + const error = new MessageV2.APIError({ + message: "Bad request", + isRetryable: false, + statusCode: 400, + }).toObject() as MessageV2.APIError + + expect(SessionRetry.retryable(error)).toBeUndefined() + }) + test("retries ZlibError decompression failures", () => { const error = new MessageV2.APIError({ message: "Response decompression failed",