vercel/ai · error · RetryError
Failed after ${tryNumber} attempts. Last error: ${errorMessa
Error message
Failed after ${tryNumber} attempts. Last error: ${errorMessage} What it means
The SDK's retry helper wraps an operation with exponential backoff. When every retry attempt fails and the number of recorded failures (tryNumber) exceeds maxRetries, it gives up and throws a retry error with reason 'maxRetriesExceeded'. The message embeds the total attempt count and the message of the last underlying error, so the real failure cause is preserved as the 'last error' text (and the original errors array is attached to the created error).
Source
Thrown at packages/provider-utils/src/retry-with-exponential-backoff.ts:102
errors: unknown[] = [],
): Promise<OUTPUT> {
try {
return await f();
} catch (error) {
if (isAbortError(error)) {
throw error; // don't retry when the request was aborted
}
if (maxRetries === 0) {
throw error; // don't wrap the error when retries are disabled
}
const errorMessage = getErrorMessage(error);
const newErrors = [...errors, error];
const tryNumber = newErrors.length;
if (tryNumber > maxRetries) {
throw createRetryError({
message: `Failed after ${tryNumber} attempts. Last error: ${errorMessage}`,
reason: 'maxRetriesExceeded',
errors: newErrors,
});
}
if ((await shouldRetry(error)) && tryNumber <= maxRetries) {
await delay(
getDelayInMs({
error,
exponentialBackoffDelay: delayInMs,
}),
{ abortSignal },
);
return retryWithExponentialBackoffInternal(
f,
{View on GitHub (pinned to 69428b1f8b)
Solutions
- Read the 'Last error: ...' portion of the message and the errors array on the thrown retry error to find the root cause, then fix that underlying error first.
- If the root cause is rate limiting, reduce request concurrency or add backoff-friendly getDelayInMs and increase maxRetries.
- If the root cause is auth or bad request, fix credentials/request before retrying; also tighten shouldRetry so non-retryable errors (e.g. 401/400) fail fast.
- If the operation is expected to fail and wrapping obscures it, set maxRetries: 0 so the original error is thrown unwrapped.
Example fix
// before: opaque wrapped failure
const result = await retryWithExponentialBackoff({ maxRetries: 5, shouldRetry })(() => fetch(url));
// after: fail fast on non-retryable statuses and log root cause
const result = await retryWithExponentialBackoff({
maxRetries: 3,
shouldRetry: ({ error }) => error?.statusCode == null || error.statusCode >= 500 || error.statusCode === 429,
})(() => fetch(url)); Defensive patterns
Strategy: try-catch
Validate before calling
// check retry budget vs expected flakiness before wrapping
if (maxRetries < 1) throw new Error('maxRetries must be >= 1 to use retryWithExponentialBackoff'); Try / catch
try {
return await retryFn(() => call());
} catch (error) {
// inspect last error text / attached errors array
const causes = (error as any)?.errors ?? [];
console.error('All attempts failed:', error.message, causes);
if (isAbortError(error)) throw error; // aborted calls are rethrown unwrapped
throw error;
} Prevention
- Set shouldRetry to only allow retryable status codes (429, 5xx, network errors) so terminal failures surface fast.
- Size maxRetries and getDelayInMs to the provider's rate-limit window.
- Log the full errors array from the wrapped retry error, not just its top message.
- Validate API keys and request payloads before the first attempt.
When it happens
Trigger: A call wrapped by retryWithExponentialBackoff (used internally by provider fetches when maxRetries > 0) throws on every attempt, and each failure passes the shouldRetry predicate, until newErrors.length > maxRetries. E.g. maxRetries: 2 means attempts 1,2 are retried and the error is thrown once tryNumber reaches 3.
Common situations: Provider API outage or sustained 429/5xx responses; invalid API key causing repeated 401s where shouldRetry still allows retrying; network connectivity loss; misconfigured maxRetries being high while the endpoint is permanently failing; request payload rejected (400) by a provider with a retryable-classification bug.
Related errors
- Failed to reconnect after ${this.maxConsecutiveErrors} conse
- Cannot connect to API: ${cause.message}
- Failed after ${tryNumber} attempts with non-retryable error:
- Video generation timed out after ${timeoutMs}ms.
- The response body is empty.
AI-assisted analysis of vercel/ai@69428b1f8b (2026-08-30).
Data as JSON: /api/errors/c3b7a3dc2ceb6187.
Report an issue: GitHub.