From 0ab19c3213cc52e42586aaacb7f8a0fcdbedb0d2 Mon Sep 17 00:00:00 2001 From: Bagnesium <152006899+Bagnesium@users.noreply.github.com> Date: Tue, 26 May 2026 12:22:29 +0500 Subject: [PATCH 1/4] fix(opencode): ignore negative retry delay hints --- packages/opencode/src/session/retry.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/opencode/src/session/retry.ts b/packages/opencode/src/session/retry.ts index 463bc27a95db..61b67804772a 100644 --- a/packages/opencode/src/session/retry.ts +++ b/packages/opencode/src/session/retry.ts @@ -31,6 +31,10 @@ function cap(ms: number) { return Math.min(ms, RETRY_MAX_DELAY) } +function isDelayHint(value: number) { + return Number.isFinite(value) && value >= 0 +} + export function delay(attempt: number, error?: MessageV2.APIError) { if (error) { const headers = error.data.responseHeaders @@ -38,15 +42,13 @@ export function delay(attempt: number, error?: MessageV2.APIError) { const retryAfterMs = headers["retry-after-ms"] if (retryAfterMs) { const parsedMs = Number.parseFloat(retryAfterMs) - if (!Number.isNaN(parsedMs)) { - return cap(parsedMs) - } + if (isDelayHint(parsedMs)) return cap(parsedMs) } const retryAfter = headers["retry-after"] if (retryAfter) { const parsedSeconds = Number.parseFloat(retryAfter) - if (!Number.isNaN(parsedSeconds)) { + if (isDelayHint(parsedSeconds)) { // convert seconds to milliseconds return cap(Math.ceil(parsedSeconds * 1000)) } From 561bc99809e2eaff8027a9bccb7a65ab29cdd957 Mon Sep 17 00:00:00 2001 From: Bagnesium <152006899+Bagnesium@users.noreply.github.com> Date: Tue, 26 May 2026 12:23:55 +0500 Subject: [PATCH 2/4] fix(opencode): ignore negative retry delay hints --- packages/opencode/test/session/retry.test.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/opencode/test/session/retry.test.ts b/packages/opencode/test/session/retry.test.ts index 22ff6cde811d..1a649c80b5d3 100644 --- a/packages/opencode/test/session/retry.test.ts +++ b/packages/opencode/test/session/retry.test.ts @@ -60,6 +60,11 @@ describe("session.retry.delay", () => { expect(SessionRetry.delay(1, error)).toBe(2000) }) + test("ignores negative retry hints", () => { + expect(SessionRetry.delay(1, apiError({ "retry-after-ms": "-1" }))).toBe(2000) + expect(SessionRetry.delay(2, apiError({ "retry-after": "-1" }))).toBe(4000) + }) + test("ignores malformed date retry hints", () => { const error = apiError({ "retry-after": "Invalid Date String" }) expect(SessionRetry.delay(1, error)).toBe(2000) From 0ba2624692c55d4491d6a1ac278d34bdf4630cf2 Mon Sep 17 00:00:00 2001 From: Bagnesium <152006899+Bagnesium@users.noreply.github.com> Date: Tue, 26 May 2026 23:45:19 +0500 Subject: [PATCH 3/4] fix(opencode): cap retry backoff when headers omit hints --- packages/opencode/src/session/retry.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/session/retry.ts b/packages/opencode/src/session/retry.ts index 61b67804772a..48d119b85c9f 100644 --- a/packages/opencode/src/session/retry.ts +++ b/packages/opencode/src/session/retry.ts @@ -31,6 +31,10 @@ function cap(ms: number) { return Math.min(ms, RETRY_MAX_DELAY) } +function backoff(attempt: number) { + return cap(Math.min(RETRY_INITIAL_DELAY * Math.pow(RETRY_BACKOFF_FACTOR, attempt - 1), RETRY_MAX_DELAY_NO_HEADERS)) +} + function isDelayHint(value: number) { return Number.isFinite(value) && value >= 0 } @@ -59,11 +63,11 @@ export function delay(attempt: number, error?: MessageV2.APIError) { } } - return cap(RETRY_INITIAL_DELAY * Math.pow(RETRY_BACKOFF_FACTOR, attempt - 1)) + return backoff(attempt) } } - return cap(Math.min(RETRY_INITIAL_DELAY * Math.pow(RETRY_BACKOFF_FACTOR, attempt - 1), RETRY_MAX_DELAY_NO_HEADERS)) + return backoff(attempt) } export function retryable(error: Err, provider: string) { From bcf2c07770a0ee0add485b4afd785898b6457308 Mon Sep 17 00:00:00 2001 From: Bagnesium <152006899+Bagnesium@users.noreply.github.com> Date: Tue, 26 May 2026 23:46:47 +0500 Subject: [PATCH 4/4] test(opencode): cover capped retry backoff with headers --- packages/opencode/test/session/retry.test.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/opencode/test/session/retry.test.ts b/packages/opencode/test/session/retry.test.ts index 1a649c80b5d3..0c788326fdf4 100644 --- a/packages/opencode/test/session/retry.test.ts +++ b/packages/opencode/test/session/retry.test.ts @@ -37,6 +37,12 @@ describe("session.retry.delay", () => { expect(delays).toStrictEqual([2000, 4000, 8000, 16000, 30000, 30000, 30000, 30000, 30000, 30000]) }) + test("caps delay at 30 seconds when headers omit retry hints", () => { + const error = apiError({ date: new Date().toUTCString() }) + const delays = Array.from({ length: 10 }, (_, index) => SessionRetry.delay(index + 1, error)) + expect(delays).toStrictEqual([2000, 4000, 8000, 16000, 30000, 30000, 30000, 30000, 30000, 30000]) + }) + test("prefers retry-after-ms when shorter than exponential", () => { const error = apiError({ "retry-after-ms": "1500" }) expect(SessionRetry.delay(4, error)).toBe(1500)