diff --git a/src/hooks/runtime-fallback/error-classifier.ts b/src/hooks/runtime-fallback/error-classifier.ts index 7ba5aa491..6b9132588 100644 --- a/src/hooks/runtime-fallback/error-classifier.ts +++ b/src/hooks/runtime-fallback/error-classifier.ts @@ -169,10 +169,9 @@ export function isRetryableError(error: unknown, retryOnErrors: number[]): boole } if (errorType === "quota_exceeded") { - // When a provider signals an auto-retry (e.g. "retrying in ~2 weeks"), - // we should still trigger fallback to another model rather than STOP. - const hasAutoRetrySignal = /retrying\s+in/i.test(message) - return hasAutoRetrySignal + // Quota exhaustion means the current model/provider cannot serve requests. + // Trigger fallback to the next configured model instead of stopping entirely. + return true } if (statusCode && retryOnErrors.includes(statusCode)) { diff --git a/src/hooks/runtime-fallback/quota-error-classifier.regression.test.ts b/src/hooks/runtime-fallback/quota-error-classifier.regression.test.ts index 0caa816a3..1979ddc30 100644 --- a/src/hooks/runtime-fallback/quota-error-classifier.regression.test.ts +++ b/src/hooks/runtime-fallback/quota-error-classifier.regression.test.ts @@ -3,7 +3,7 @@ import { describe, expect, test } from "bun:test" import { classifyErrorType, isRetryableError } from "./error-classifier" describe("runtime-fallback quota error regressions", () => { - test("classifies subscription quota errors as quota_exceeded and stops retry", () => { + test("classifies subscription quota errors as quota_exceeded and triggers fallback", () => { //#given const error = { name: "AI_APICallError", @@ -16,10 +16,11 @@ describe("runtime-fallback quota error regressions", () => { //#then expect(errorType).toBe("quota_exceeded") - expect(retryable).toBe(false) + // quota exhaustion should trigger fallback to the next model + expect(retryable).toBe(true) }) - test("treats HTTP 402 payment required as non-retryable", () => { + test("treats HTTP 402 payment required as fallback-eligible", () => { //#given const error = { statusCode: 402, message: "Payment Required" } @@ -27,7 +28,8 @@ describe("runtime-fallback quota error regressions", () => { const retryable = isRetryableError(error, [429, 500, 502, 503, 504]) //#then - expect(retryable).toBe(false) + // payment failure triggers fallback to a different provider/model + expect(retryable).toBe(true) }) test("keeps HTTP 429 rate limit retryable", () => { @@ -41,7 +43,7 @@ describe("runtime-fallback quota error regressions", () => { expect(retryable).toBe(true) }) - test("classifies quota error names as quota_exceeded without retry", () => { + test("classifies quota error names as quota_exceeded and triggers fallback", () => { //#given const error = { name: "QuotaExceededError", message: "Request failed." } @@ -51,6 +53,7 @@ describe("runtime-fallback quota error regressions", () => { //#then expect(errorType).toBe("quota_exceeded") - expect(retryable).toBe(false) + // quota errors trigger fallback to next configured model + expect(retryable).toBe(true) }) })