diff --git a/src/shared/model-error-classifier.test.ts b/src/shared/model-error-classifier.test.ts index 28fbf4047..fdaf14ba2 100644 --- a/src/shared/model-error-classifier.test.ts +++ b/src/shared/model-error-classifier.test.ts @@ -150,6 +150,50 @@ describe("model-error-classifier", () => { expect(result).toBe(false) }) + test("treats quota reset message as non-retryable STOP error (no error name)", () => { + //#given + const error = { message: "quota will reset after 1 hour" } + + //#when + const result = shouldRetryError(error) + + //#then + expect(result).toBe(false) + }) + + test("treats quota exceeded message as non-retryable STOP error (no error name)", () => { + //#given + const error = { message: "quota exceeded for this billing period" } + + //#when + const result = shouldRetryError(error) + + //#then + expect(result).toBe(false) + }) + + test("treats usage limit reached message as non-retryable STOP error (no error name)", () => { + //#given + const error = { message: "usage limit has been reached for your account" } + + //#when + const result = shouldRetryError(error) + + //#then + expect(result).toBe(false) + }) + + test("treats insufficient credits message as non-retryable STOP error (no error name)", () => { + //#given + const error = { message: "insufficient credits to complete this request" } + + //#when + const result = shouldRetryError(error) + + //#then + expect(result).toBe(false) + }) + test("treats 'bad request' message as retryable (GitHub Copilot rolling update)", () => { //#given const error = { message: "400 Bad Request" } diff --git a/src/shared/model-error-classifier.ts b/src/shared/model-error-classifier.ts index 29a0cb801..cbd0b8c12 100644 --- a/src/shared/model-error-classifier.ts +++ b/src/shared/model-error-classifier.ts @@ -40,8 +40,6 @@ const RETRYABLE_MESSAGE_PATTERNS = [ "rate_limit", "rate limit", "quota", - "quota will reset after", - "usage limit has been reached", "all credentials for model", "cooling down", "exhausted your capacity", @@ -76,6 +74,25 @@ const RETRYABLE_MESSAGE_PATTERNS = [ "529", ] +/** + * Message patterns that indicate a non-retryable STOP error (quota/billing exhaustion). + * These take precedence over RETRYABLE_MESSAGE_PATTERNS. + */ +const STOP_MESSAGE_PATTERNS = [ + "quota will reset after", + "quota exceeded", + "usage limit has been reached", + "free usage limit", + "billing limit", + "monthly limit", + "plan limit", + "subscription limit", + "out of credits", + "credits exhausted", + "insufficient credits", + "insufficient balance", +] + const AUTO_RETRY_GATE_PATTERNS = [ "rate limit", "quota", @@ -121,6 +138,12 @@ export function isRetryableModelError(error: ErrorInfo): boolean { // Check message patterns for unknown errors const msg = error.message?.toLowerCase() ?? "" + + // STOP patterns take precedence over retryable patterns + if (STOP_MESSAGE_PATTERNS.some((pattern) => msg.includes(pattern))) { + return false + } + if (hasProviderAutoRetrySignal(msg)) { return true }