fix(runtime-fallback): trigger fallback on quota/credit exhaustion (fixes #3519)

This commit is contained in:
MoerAI
2026-04-21 18:17:22 +09:00
parent 93dc103092
commit 7488c527df
2 changed files with 12 additions and 10 deletions
@@ -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)) {
@@ -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)
})
})