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:
@@ -150,6 +150,50 @@ describe("model-error-classifier", () => {
|
|||||||
expect(result).toBe(false)
|
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)", () => {
|
test("treats 'bad request' message as retryable (GitHub Copilot rolling update)", () => {
|
||||||
//#given
|
//#given
|
||||||
const error = { message: "400 Bad Request" }
|
const error = { message: "400 Bad Request" }
|
||||||
|
|||||||
@@ -40,8 +40,6 @@ const RETRYABLE_MESSAGE_PATTERNS = [
|
|||||||
"rate_limit",
|
"rate_limit",
|
||||||
"rate limit",
|
"rate limit",
|
||||||
"quota",
|
"quota",
|
||||||
"quota will reset after",
|
|
||||||
"usage limit has been reached",
|
|
||||||
"all credentials for model",
|
"all credentials for model",
|
||||||
"cooling down",
|
"cooling down",
|
||||||
"exhausted your capacity",
|
"exhausted your capacity",
|
||||||
@@ -76,6 +74,25 @@ const RETRYABLE_MESSAGE_PATTERNS = [
|
|||||||
"529",
|
"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 = [
|
const AUTO_RETRY_GATE_PATTERNS = [
|
||||||
"rate limit",
|
"rate limit",
|
||||||
"quota",
|
"quota",
|
||||||
@@ -121,6 +138,12 @@ export function isRetryableModelError(error: ErrorInfo): boolean {
|
|||||||
|
|
||||||
// Check message patterns for unknown errors
|
// Check message patterns for unknown errors
|
||||||
const msg = error.message?.toLowerCase() ?? ""
|
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)) {
|
if (hasProviderAutoRetrySignal(msg)) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user