Skip to content

Errors and retries

How the package retries failed requests, which exceptions it throws, and how failover works.

Updated

What the package retries

The package retries a request when a second try can succeed. The default is 3 attempts in total, including the first. The same rules apply to chat, embeddings, Clef and Clairic.

Failure Retried Result after the last attempt
Connect-phase network error (cURL 6, 7, 56, or a connect timeout) Yes ProviderConnectionException
Timeout during transfer (cURL 28 mid-request) No ProviderConnectionException on laravel/ai 0.11 and later. Illuminate ConnectionException on 0.9 and 0.10.
HTTP 402 No InsufficientCreditsException
HTTP 408 No GatewayTimeoutException
HTTP 429 Yes, with backoff RateLimitedException
HTTP 502, 503, 504 Yes ProviderOverloadedException
HTTP 520, 522, 524 Yes ProviderOverloadedException
HTTP 200 with no text and no tool calls Not applicable EmptyResponseException
HTTP 200 and finish reason length Not applicable TruncatedResponseException, only if throw_on_truncation is on

Backoff is 500 ms, 1 s, 2 s and so on, up to 20 s. If the response has a Retry-After header, the package waits that long, up to 20 s. It reads both seconds and HTTP-date values.

On laravel/ai 1.2.1 and later, Laravel AI has its own retry for a short Retry-After. The package turns that retry off, so a persistent 429 makes 3 attempts, not 6.

Retry settings

'workers-ai' => [
    // ...
    'retry'              => true,  // false turns all retrying off
    'retry_attempts'     => 3,     // total attempts, including the first
    'retry_rate_limited' => true,  // false makes a 429 fail at once
],

Why a timeout is not retried

A request that hits your client timeout has already run for the whole timeout, and a retry would run as long. Three attempts would turn a 60-second timeout into about 3 minutes. Raise #[Timeout] instead. See Text generation.

HTTP 408

A 408 from the gateway is not a client timeout. The request reached Cloudflare, and the generation ran longer than the gateway waits. Measured on 2026-09-09 on one account: @cf/zai-org/glm-5.3-flash with a 24,000 token cap returned 408 after 709 seconds.

The package throws GatewayTimeoutException and does not retry, because a retry takes as long. It also throws this exception for a 408 from Clef or Clairic. To fix it, lower the token cap, lower reasoning_effort, split the work, or set a failover provider.

HTTP 429

Laravel AI does not retry a 429. This package does, so one 429 in a wide fan-out does not lose a request.

The package does not limit your concurrency. Limits depend on the account and the model. Limit the fan-out in your own code, for example with a queue rate limiter.

Failover

These exceptions implement the Laravel AI FailoverableException marker: GatewayTimeoutException, RateLimitedException, ProviderOverloadedException, InsufficientCreditsException and the connection exceptions. If you set a fallback provider in Laravel AI, it takes over when one of them is thrown.

These do not trigger failover: EmptyResponseException and TruncatedResponseException. Catch them in your code. Both extend Laravel\Ai\Exceptions\AiException.

use Laravel\Ai\Exceptions\AiException;
use Meirdick\WorkersAi\Exceptions\EmptyResponseException;

try {
    $response = $agent->prompt('...', provider: 'workers-ai');
} catch (EmptyResponseException $e) {
    // Raise the token cap, or lower reasoning_effort, and try again.
} catch (AiException $e) {
    // Any other Laravel AI failure.
}

Other HTTP errors

The package maps only the statuses in the table above. Any other non-2xx status, such as 400, 401, 403, 404, 410 or 413, throws Illuminate\Http\Client\RequestException. A bad API token gives this error. The message looks like HTTP request returned status code 401: {"code":10000,"message":"Authentication error"}. These errors do not trigger failover. Read the status with $e->response->status() and the body with $e->response->json().

use Illuminate\Http\Client\RequestException;

try {
    $response = $agent->prompt('...', provider: 'workers-ai');
} catch (RequestException $e) {
    report($e->response->status());
}

Error payloads in a 200 response

Cloudflare uses two error shapes: {"error": {...}} and {"errors": [...]}. When an HTTP 200 response carries either one, the package throws AiException with a message in this form: Workers AI Error: [type] message.

If a chat response has no choices, the package throws AiException with Workers AI Error: Response did not contain any choices.

Clef and Clairic errors

Clef and Clairic call POST /ai/run/{model}. The package reads a reply wrapped in {result, success, errors} and an unwrapped reply.

Reply Result
HTTP 200 with a non-empty errors, or success: false AiException, message Workers AI Error: [type] message
A reply with no answers AiException, message Workers AI Error: Response contained no answers.
HTTP 408 GatewayTimeoutException

The other statuses follow the table at the top of this page. See Clef and Clairic.

Gateway retry

If your AI Gateway also retries, the two layers multiply the attempts. Use one layer. To use the gateway retry, set retry to false.

Type to search…

↑↓ navigate↵ selectEsc close