The backoff has run out of tries. After the last try, Laravel stores the job in failed_jobs. You can inspect it and run it again once the cause is fixed. A failed job is a record, not a dead end.
Queues in production · Lesson 2
Failed jobs you can replay
Video
01 Replay one, or all of them
failed() runs once, when the job has given up. Keep it small: report the exception, and leave the order row alone if handle() might have written it already.
class Example
{
public function failed(Throwable $exception): void
{
report($exception);
}
}php artisan queue:failed
php artisan queue:retry 8f3c
php artisan queue:retry allRead the exception before you retry. Until handle() is safe to run twice — lesson 8 — don't replay a job that charges a card. Retry is safe when handle() can run twice and still leave the order paid once.
02 Keep the order id next to the exception
report() stores the exception. It does not, by itself, say which order it belonged to. failed() still has the constructor arguments, so the log line can name them. Leave the order row alone when handle() might have written it already. A second write here can mark a paid order as failed.
class Example
{
public function failed(Throwable $exception): void
{
logger()->error('MarkOrderPaid gave up', [
'order_id' => $this->orderId,
'attempt' => $this->attempt,
'message' => $exception->getMessage(),
]);
report($exception);
}
}queue:retry copies that same payload back onto the queue. It does not build a new attempt key. If the logged attempt is already on the order, the idempotent handle() returns and the retry is a no-op. That is the outcome you want after a worker died at the end of a successful charge.
Recap
You can read a failed job and retry the same payload once handle() is safe to run again.
failed()runs once, when the tries are spent.queue:retrycopies the payload. It does not mint a new attempt UUID.- Do not write the order again from
failed()ifhandle()might have written it.
Check yourself · 3 questions
Question 1 of 3queue:retry runs the same payment UUID that is already on the order. What should happen?