fix(runtime-fallback): gate retryable signal on status-code allowlist
extractRetryableSignal returns the raw isRetryable hint from up to 5
nested AI SDK error paths. isRetryableError previously trusted any
true result blindly, which would burn every configured fallback model
in an infinite loop if a provider mis-tagged a 401, 403, or other
non-transient 4xx as retryable.
Honor the signal only when the status code is absent, in the configured
retry_on_errors list, in 5xx, or in {408, 425, 429}. Reject the signal
when the status code is a non-transient 4xx and log the rejection so
operators can debug provider mis-classifications.
Closes pre-publish blocker V8.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { DEFAULT_CONFIG, RETRYABLE_ERROR_PATTERNS } from "./constants"
|
||||
import { DEFAULT_CONFIG, HOOK_NAME, RETRYABLE_ERROR_PATTERNS } from "./constants"
|
||||
import { log } from "../../shared/logger"
|
||||
|
||||
export { extractAutoRetrySignal } from "./auto-retry-signal"
|
||||
|
||||
@@ -119,6 +120,10 @@ export function extractRetryableSignal(error: unknown): boolean | undefined {
|
||||
return undefined
|
||||
}
|
||||
|
||||
function isStatusCodeRetrySafe(code: number, retryOnErrors: number[]): boolean {
|
||||
return retryOnErrors.includes(code) || (code >= 500 && code < 600) || code === 408 || code === 425 || code === 429
|
||||
}
|
||||
|
||||
function isLocalizedQuotaExhaustionMessage(message: string): boolean {
|
||||
return (
|
||||
(/预扣费额度失败/i.test(message) && /用户剩余额度/i.test(message)) ||
|
||||
@@ -221,8 +226,16 @@ export function isRetryableError(error: unknown, retryOnErrors: number[]): boole
|
||||
return true
|
||||
}
|
||||
|
||||
if (extractRetryableSignal(error) === true) {
|
||||
return true
|
||||
const retryableSignal = extractRetryableSignal(error)
|
||||
if (retryableSignal === true) {
|
||||
if (statusCode === undefined || isStatusCodeRetrySafe(statusCode, retryOnErrors)) {
|
||||
return true
|
||||
}
|
||||
|
||||
log(`[${HOOK_NAME}] Retryable signal rejected due to unsafe status code`, {
|
||||
statusCode,
|
||||
retryOnErrors,
|
||||
})
|
||||
}
|
||||
|
||||
return RETRYABLE_ERROR_PATTERNS.some((pattern) => pattern.test(message))
|
||||
|
||||
Reference in New Issue
Block a user