From 1c7881ec09893dc57f4e02aae3ead64ec932bca0 Mon Sep 17 00:00:00 2001 From: wenghuayang863 <381421746@qq.com> Date: Mon, 11 May 2026 00:43:58 +0800 Subject: [PATCH 1/2] fix(runtime-fallback): match Volcano Engine 'exceeded the usage quota' errors Volcano Engine sends quota exceeded errors with the words in reverse order: 'You have exceeded the 5-hour usage quota'. The existing patterns required 'quota' to precede 'exceeded', so they never matched. - Add /exceeded.*quota/i and /usage.?quota/i to RETRYABLE_ERROR_PATTERNS - Add exceeded.*quota and usage\s*quota to AUTO_RETRY_PATTERNS - Add regression tests for both detection paths Fixes: runtime-fallback not triggering on Volcano Engine quota errors --- .../auto-retry-signal.test.ts | 41 +++++++++++++++++++ .../runtime-fallback/auto-retry-signal.ts | 2 +- src/hooks/runtime-fallback/constants.ts | 2 + .../quota-error-classifier.regression.test.ts | 15 +++++++ 4 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 src/hooks/runtime-fallback/auto-retry-signal.test.ts diff --git a/src/hooks/runtime-fallback/auto-retry-signal.test.ts b/src/hooks/runtime-fallback/auto-retry-signal.test.ts new file mode 100644 index 000000000..e734b309b --- /dev/null +++ b/src/hooks/runtime-fallback/auto-retry-signal.test.ts @@ -0,0 +1,41 @@ +import { describe, expect, test } from "bun:test" + +import { extractAutoRetrySignal } from "./auto-retry-signal" + +describe("extractAutoRetrySignal", () => { + test("detects Volcano Engine 'exceeded the usage quota' signal", () => { + //#given + const info = { + status: "You have exceeded the 5-hour usage quota. It will reset at 2026-05-11 01:20:12 +0800 CST.", + } + + //#when + const signal = extractAutoRetrySignal(info) + + //#then + expect(signal).toBeDefined() + expect(signal?.signal).toContain("exceeded") + }) + + test("detects standard 'quota exceeded' signal", () => { + //#given + const info = { message: "Quota exceeded for model gpt-4" } + + //#when + const signal = extractAutoRetrySignal(info) + + //#then + expect(signal).toBeDefined() + }) + + test("returns undefined for non-retryable info", () => { + //#given + const info = { message: "Something went wrong" } + + //#when + const signal = extractAutoRetrySignal(info) + + //#then + expect(signal).toBeUndefined() + }) +}) diff --git a/src/hooks/runtime-fallback/auto-retry-signal.ts b/src/hooks/runtime-fallback/auto-retry-signal.ts index 1d33edbee..9e2e9ab67 100644 --- a/src/hooks/runtime-fallback/auto-retry-signal.ts +++ b/src/hooks/runtime-fallback/auto-retry-signal.ts @@ -5,7 +5,7 @@ export interface AutoRetrySignal { const AUTO_RETRY_PATTERNS: Array<(combined: string) => boolean> = [ (combined) => /retrying\s+in/i.test(combined), (combined) => - /(?:too\s+many\s+requests|quota\s+will\s+reset\s+after|quota\s*exceeded|usage\s+limit|rate\s+limit|limit\s+reached|all\s+credentials\s+for\s+model|cool(?:ing)?\s*down|exhausted\s+your\s+capacity)/i.test(combined), + /(?:too\s+many\s+requests|quota\s+will\s+reset\s+after|quota\s*exceeded|exceeded.*quota|usage\s+limit|usage\s*quota|rate\s+limit|limit\s+reached|all\s+credentials\s+for\s+model|cool(?:ing)?\s*down|exhausted\s+your\s+capacity)/i.test(combined), ] export function extractAutoRetrySignal(info: Record | undefined): AutoRetrySignal | undefined { diff --git a/src/hooks/runtime-fallback/constants.ts b/src/hooks/runtime-fallback/constants.ts index 19a7cad56..e835b4099 100644 --- a/src/hooks/runtime-fallback/constants.ts +++ b/src/hooks/runtime-fallback/constants.ts @@ -27,6 +27,8 @@ export const RETRYABLE_ERROR_PATTERNS = [ /too.?many.?requests/i, /quota\s+will\s+reset\s+after/i, /quota.?exceeded/i, + /exceeded.*quota/i, + /usage.?quota/i, /exhausted\s+your\s+capacity/i, /all\s+credentials\s+for\s+model/i, /cool(?:ing)?\s+down/i, 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 1979ddc30..737db208c 100644 --- a/src/hooks/runtime-fallback/quota-error-classifier.regression.test.ts +++ b/src/hooks/runtime-fallback/quota-error-classifier.regression.test.ts @@ -56,4 +56,19 @@ describe("runtime-fallback quota error regressions", () => { // quota errors trigger fallback to next configured model expect(retryable).toBe(true) }) + + test("classifies Volcano Engine 'exceeded the usage quota' as retryable", () => { + //#given + const error = { + name: "SessionRetry", + message: "You have exceeded the 5-hour usage quota. It will reset at 2026-05-11 01:20:12 +0800 CST. We recommend using a different model.", + } + + //#when + const retryable = isRetryableError(error, [429, 500, 502, 503, 504]) + + //#then + // Volcano Engine quota errors trigger fallback to the next model + expect(retryable).toBe(true) + }) }) From f3f72fc96f2b0a1f789eff12187b4b50cdeb9116 Mon Sep 17 00:00:00 2001 From: wenghuayang863 <381421746@qq.com> Date: Mon, 11 May 2026 01:03:02 +0800 Subject: [PATCH 2/2] fix(runtime-fallback): also classify Volcano Engine errors as quota_exceeded - Add /exceeded.*quota/i and /usage\s*quota/i to classifyErrorType quota block - Align /usage.?quota/i -> /usage\s*quota/i in RETRYABLE_ERROR_PATTERNS for consistency - Strengthen auto-retry-signal test assertion - Add classifyErrorType assertion to Volcano Engine regression test Ensures Volcano Engine errors are both retryable AND logged as errorType: quota_exceeded. --- src/hooks/runtime-fallback/auto-retry-signal.test.ts | 1 + src/hooks/runtime-fallback/constants.ts | 2 +- src/hooks/runtime-fallback/error-classifier.ts | 2 ++ .../quota-error-classifier.regression.test.ts | 4 +++- 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/hooks/runtime-fallback/auto-retry-signal.test.ts b/src/hooks/runtime-fallback/auto-retry-signal.test.ts index e734b309b..e485fbd6c 100644 --- a/src/hooks/runtime-fallback/auto-retry-signal.test.ts +++ b/src/hooks/runtime-fallback/auto-retry-signal.test.ts @@ -15,6 +15,7 @@ describe("extractAutoRetrySignal", () => { //#then expect(signal).toBeDefined() expect(signal?.signal).toContain("exceeded") + expect(signal?.signal).toContain("usage quota") }) test("detects standard 'quota exceeded' signal", () => { diff --git a/src/hooks/runtime-fallback/constants.ts b/src/hooks/runtime-fallback/constants.ts index e835b4099..f407ffea0 100644 --- a/src/hooks/runtime-fallback/constants.ts +++ b/src/hooks/runtime-fallback/constants.ts @@ -28,7 +28,7 @@ export const RETRYABLE_ERROR_PATTERNS = [ /quota\s+will\s+reset\s+after/i, /quota.?exceeded/i, /exceeded.*quota/i, - /usage.?quota/i, + /usage\s*quota/i, /exhausted\s+your\s+capacity/i, /all\s+credentials\s+for\s+model/i, /cool(?:ing)?\s+down/i, diff --git a/src/hooks/runtime-fallback/error-classifier.ts b/src/hooks/runtime-fallback/error-classifier.ts index 614023f1c..3bb46454c 100644 --- a/src/hooks/runtime-fallback/error-classifier.ts +++ b/src/hooks/runtime-fallback/error-classifier.ts @@ -126,6 +126,8 @@ export function classifyErrorType(error: unknown): string | undefined { errorName?.includes("insufficientquota") || errorName?.includes("billingerror") || /quota.?exceeded/i.test(message) || + /exceeded.*quota/i.test(message) || + /usage\s*quota/i.test(message) || /subscription.*quota/i.test(message) || /insufficient.?(?:quota|balance|funds?)/i.test(message) || /billing.?(?:hard.?)?limit/i.test(message) || 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 737db208c..5878e8f2a 100644 --- a/src/hooks/runtime-fallback/quota-error-classifier.regression.test.ts +++ b/src/hooks/runtime-fallback/quota-error-classifier.regression.test.ts @@ -57,7 +57,7 @@ describe("runtime-fallback quota error regressions", () => { expect(retryable).toBe(true) }) - test("classifies Volcano Engine 'exceeded the usage quota' as retryable", () => { + test("classifies Volcano Engine 'exceeded the usage quota' as quota_exceeded and retryable", () => { //#given const error = { name: "SessionRetry", @@ -65,9 +65,11 @@ describe("runtime-fallback quota error regressions", () => { } //#when + const errorType = classifyErrorType(error) const retryable = isRetryableError(error, [429, 500, 502, 503, 504]) //#then + expect(errorType).toBe("quota_exceeded") // Volcano Engine quota errors trigger fallback to the next model expect(retryable).toBe(true) })