From 9245279de5f4d34636bebf771fe8943106373fab Mon Sep 17 00:00:00 2001 From: Christiaan Arnoldus Date: Thu, 30 Jul 2026 18:55:05 +0200 Subject: [PATCH] fix: match hyphenated rate-limit prose in retry heuristics --- packages/opencode/src/kilocode/provider/error.ts | 2 +- .../opencode/test/kilocode/provider/error.test.ts | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/kilocode/provider/error.ts b/packages/opencode/src/kilocode/provider/error.ts index f6550544f43..dc8e8c85f7d 100644 --- a/packages/opencode/src/kilocode/provider/error.ts +++ b/packages/opencode/src/kilocode/provider/error.ts @@ -51,7 +51,7 @@ export function frame(body: unknown): Frame { const RETRYABLE = /rate.?limit|too.?many.?requests|rate increased too quickly|exhausted|overload|server|unavailable|timeout/i // Session.retryable only matched these phrases against free-form message // text; the wider pattern above is for structured code/type fields only -const RETRYABLE_TEXT = /rate increased too quickly|rate limit|too many requests/i +const RETRYABLE_TEXT = /rate increased too quickly|rate.?limit|too.?many.?requests/i // Must stay at least as permissive as the Session.retryable heuristics that // applied when these frames still surfaced as NamedError.Unknown, or diff --git a/packages/opencode/test/kilocode/provider/error.test.ts b/packages/opencode/test/kilocode/provider/error.test.ts index 8139abdcc06..ebc4fa393b5 100644 --- a/packages/opencode/test/kilocode/provider/error.test.ts +++ b/packages/opencode/test/kilocode/provider/error.test.ts @@ -226,6 +226,18 @@ describe("responses api terminal frames", () => { expect(result.data.isRetryable).toBe(false) }) + test("retries hyphenated rate-limit prose", () => { + const payload = { + code: "upstream_error", + message: "openai/gpt-5.6-terra-pro is temporarily rate-limited upstream. Please retry shortly.", + } + const result = MessageV2.fromError(payload, { providerID: ProviderV2.ID.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) + }) + test("ignores response.failed frames without an error payload", () => { const payload = { type: "response.failed", response: { error: null, incomplete_details: null } } const result = MessageV2.fromError(payload, { providerID: ProviderV2.ID.make("openai") })