From aeb9c97c3064fbd5c79d4443580a4925c7d7ed07 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Sun, 5 Apr 2026 09:13:14 +0900 Subject: [PATCH] fix(model): quota errors should STOP not retry --- src/shared/model-error-classifier.test.ts | 52 +++++++++++++++++++++-- src/shared/model-error-classifier.ts | 10 ++++- 2 files changed, 56 insertions(+), 6 deletions(-) diff --git a/src/shared/model-error-classifier.test.ts b/src/shared/model-error-classifier.test.ts index 812b21e07..5dc9fbeec 100644 --- a/src/shared/model-error-classifier.test.ts +++ b/src/shared/model-error-classifier.test.ts @@ -84,7 +84,51 @@ describe("model-error-classifier", () => { expect(provider).toBe("provider-x") }) - test("treats FreeUsageLimitError (PascalCase name) as retryable by name", () => { + test("treats QuotaExceededError (PascalCase name) as non-retryable STOP error", () => { + //#given + const error = { name: "QuotaExceededError" } + + //#when + const result = shouldRetryError(error) + + //#then + expect(result).toBe(false) + }) + + test("treats quotaexceedederror (lowercase name) as non-retryable STOP error", () => { + //#given + const error = { name: "quotaexceedederror" } + + //#when + const result = shouldRetryError(error) + + //#then + expect(result).toBe(false) + }) + + test("treats InsufficientCreditsError (PascalCase name) as non-retryable STOP error", () => { + //#given + const error = { name: "InsufficientCreditsError" } + + //#when + const result = shouldRetryError(error) + + //#then + expect(result).toBe(false) + }) + + test("treats insufficientcreditserror (lowercase name) as non-retryable STOP error", () => { + //#given + const error = { name: "insufficientcreditserror" } + + //#when + const result = shouldRetryError(error) + + //#then + expect(result).toBe(false) + }) + + test("treats FreeUsageLimitError (PascalCase name) as non-retryable STOP error", () => { //#given const error = { name: "FreeUsageLimitError" } @@ -92,10 +136,10 @@ describe("model-error-classifier", () => { const result = shouldRetryError(error) //#then - expect(result).toBe(true) + expect(result).toBe(false) }) - test("treats freeusagelimiterror (lowercase name) as retryable by name", () => { + test("treats freeusagelimiterror (lowercase name) as non-retryable STOP error", () => { //#given const error = { name: "freeusagelimiterror" } @@ -103,7 +147,7 @@ describe("model-error-classifier", () => { const result = shouldRetryError(error) //#then - expect(result).toBe(true) + expect(result).toBe(false) }) }) diff --git a/src/shared/model-error-classifier.ts b/src/shared/model-error-classifier.ts index 5868aefec..fe4eeac3f 100644 --- a/src/shared/model-error-classifier.ts +++ b/src/shared/model-error-classifier.ts @@ -8,11 +8,14 @@ import { readConnectedProvidersCache } from "./connected-providers-cache" const RETRYABLE_ERROR_NAMES = new Set([ "providermodelnotfounderror", "ratelimiterror", - "quotaexceedederror", - "insufficientcreditserror", "modelunavailableerror", "providerconnectionerror", "authenticationerror", +]) + +const STOP_ERROR_NAMES = new Set([ + "quotaexceedederror", + "insufficientcreditserror", "freeusagelimiterror", ]) @@ -106,6 +109,9 @@ export function isRetryableModelError(error: ErrorInfo): boolean { if (NON_RETRYABLE_ERROR_NAMES.has(errorNameLower)) { return false } + if (STOP_ERROR_NAMES.has(errorNameLower)) { + return false + } // Check if it's a known retryable error if (RETRYABLE_ERROR_NAMES.has(errorNameLower)) { return true