fix(error-classifier): quota/billing errors are non-retryable STOP (fixes #3126)

Add STOP_MESSAGE_PATTERNS that take precedence over RETRYABLE_MESSAGE_PATTERNS.
Message-only quota errors (no error name) now correctly return false from
isRetryableModelError, preventing unnecessary fallback retries on exhausted quotas.

- quota will reset after...
- quota exceeded
- usage limit has been reached
- free usage limit / billing limit / plan limit / subscription limit
- out of credits / credits exhausted / insufficient credits / insufficient balance

Also add 4 regression tests covering message-only quota STOP cases.
This commit is contained in:
YeonGyu-Kim
2026-04-06 16:56:05 +09:00
parent 9438c89c1c
commit f69234ab7e
2 changed files with 69 additions and 2 deletions
+25 -2
View File
@@ -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
}