summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--packages/opencode/src/session/retry.ts5
-rw-r--r--packages/opencode/test/session/retry.test.ts41
2 files changed, 45 insertions, 1 deletions
diff --git a/packages/opencode/src/session/retry.ts b/packages/opencode/src/session/retry.ts
index 39eb8cfb7..6aad55f3f 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 314306ba6..2d01a8f35 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",