An event listener marked as queued, doing 20 seconds of PDF generation, and failing in a way that made the original event look like the problem.
// what it was
class GenerateInvoicePdf implements ShouldQueue
{
public function handle(OrderPlaced $event): void { /* 20s */ }
}
// what it should have been
class OrderPlacedListener
{
public function handle(OrderPlaced $event): void
{
GenerateInvoicePdf::dispatch($event->orderId);
}
}
// the difference: the job has its own name in the failed table,
// its own retry policy, and can be dispatched by hand.
A queued listener is a job wearing an event’s name, and the failed-jobs row says the event rather than the work. Retrying it re-runs the listener with a serialised event, which is usually what you want and occasionally is not — if three listeners are queued from one event, retrying the failure retries only that listener, which is correct and completely non-obvious from the record. A named job is one more class and removes the whole question.