From fd6c38637b3406e9610f3c8cdc583c72090f5a3d Mon Sep 17 00:00:00 2001 From: diegosouzapw Date: Tue, 4 Aug 2026 05:56:48 -0300 Subject: [PATCH] fix(classify429): add missing 'exhausted their quota' pattern to prevent combo fallback failure (#9269) --- changelog.d/fixes/9269-fix.plan.md | 1 + src/shared/utils/classify429.ts | 8 ++++++++ tests/unit/classify429.test.ts | 7 +++++++ 3 files changed, 16 insertions(+) create mode 100644 changelog.d/fixes/9269-fix.plan.md diff --git a/changelog.d/fixes/9269-fix.plan.md b/changelog.d/fixes/9269-fix.plan.md new file mode 100644 index 0000000000..147c32be70 --- /dev/null +++ b/changelog.d/fixes/9269-fix.plan.md @@ -0,0 +1 @@ +- **fix(classify429):** add missing `have exhausted their quota` pattern so the synthetic 429 from auth.ts is recognized as quota exhaustion, preventing the combo loop from burning retries against the same provider instead of falling back to a healthy one ([#9269](https://github.com/diegosouzapw/OmniRoute/issues/9269)) diff --git a/src/shared/utils/classify429.ts b/src/shared/utils/classify429.ts index 6c6f4eee2e..fba8cdaee6 100644 --- a/src/shared/utils/classify429.ts +++ b/src/shared/utils/classify429.ts @@ -66,6 +66,14 @@ const QUOTA_PATTERNS: ReadonlyArray = [ // the 429 is misclassified as transient rate_limit and retried every // ~60s against a budget that only resets at UTC midnight. /daily free allocation/i, + + // OmniRoute auth-layer synthetic 429 (Issue #9269). + // Body: "All antigravity accounts have exhausted their quota (reset after 5m)" + // Produced by auth.ts line 1477 when every account for a provider has + // exhausted its quota. Without this pattern, the message is classified as + // a transient rate-limit and the combo loop burns retries against the + // same provider instead of falling back to a healthy one. + /have exhausted their quota/i, ]; /** diff --git a/tests/unit/classify429.test.ts b/tests/unit/classify429.test.ts index 234c42ee73..83d87cd63f 100644 --- a/tests/unit/classify429.test.ts +++ b/tests/unit/classify429.test.ts @@ -44,6 +44,13 @@ test("classify429: Antigravity 'Individual quota reached' body returns 'quota_ex assert.equal(classify429({ status: 429, body: { error: { message: body } } }), "quota_exhausted"); }); +test("classify429: auth-layer synthetic 'have exhausted their quota' returns 'quota_exhausted' (#9269)", () => { + const body = "All antigravity accounts have exhausted their quota (reset after 5m)"; + assert.equal(looksLikeQuotaExhausted(body), true); + assert.equal(classify429({ status: 429, body }), "quota_exhausted"); + assert.equal(classify429({ status: 429, body: { error: { message: body } } }), "quota_exhausted"); +}); + test("classify429: Google RESOURCE_EXHAUSTED with a billing-period reset is quota exhausted", () => { const body = "Resource has been exhausted (e.g. check quota). (reset after 24h)"; assert.equal(looksLikeQuotaExhausted(body), true);