What Changed in Laravel 13.30
Before Laravel 13.30, a queue:work process could exit for nine different reasons and the console output looked identical every time. If a worker managed by Supervisor restarted every few minutes, you had no quick way to tell whether it hit its memory limit, lost its database connection, or picked up a queue:restart signal from a deploy. You had to register an event listener or correlate the restart time against a deploy log.
Starting with Laravel 13.30, the worker prints a human-readable reason on its last output line:
2026-09-01 13:20:40 Worker STOPPED Memory limit exceeded
Contributed by Jack Bayliss in PR #61339.
Console and JSON Output
WorkCommand now registers a listener for the WorkerStopping event alongside the existing job and queue status listeners. The stop line appears in the same two formats as the rest of the output.
With --json, the stop reason is a structured record:
{"level":"info","status":"stopped","reason":"empty","exit_code":0,"jobs_processed":12,"memory":34.0,"timestamp":"2026-09-01T13:20:40.118273+00:00"}
Key fields to know:
reason— the enum's backing value; stable to match on in scripts or log queries.memory— megabytes rounded to one decimal, ornullwhen no figure was recorded.level—infofor exit code 0,warningfor anything else.
The stop line is suppressed under --quiet and --silent, and when the reason is null. Workers killed by an OOM killer or SIGKILL never reach the event dispatch code, so those exits remain silent.
All Nine Stop Reasons
WorkerStopReason is an existing enum. This change added a description() method to it so the console output and any listener you write share the same strings.
| Value | Description | Exit code |
|---|---|---|
| empty | Queue empty | 0 |
| empty_for | Queue empty for the configured duration | 0 |
| max_jobs | Maximum jobs exceeded | 0 |
| max_time | Maximum run time exceeded | 0 |
| restart_signal | Received restart signal | 0 |
| interrupted | Interrupted | 0 |
| lost_connection | Lost connection | 0 |
| memory | Memory limit exceeded | 12 |
| timed_out | Job timed out | 1 |
Filtering JSON Output with jq
Because the reason is now a discrete field, isolating stop events from a long log stream is a one-liner:
php artisan queue:work --json --max-time=3600 2>&1 | \
jq -c 'select(.status == "stopped")'
Under a process manager writing to a file, the same jq query runs against the log after the fact.
Using the WorkerStopping Event
For anything beyond reading output—dashboards, metrics backends, alerting—the WorkerStopping event is still the right hook. It carries the reason, exit status, jobs processed, last-job timestamp, and memory in use:
use Illuminate\Queue\Events\WorkerStopping;
Event::listen(function (WorkerStopping $event) {
Log::info('Worker stopped', [
'reason' => $event->reason?->value,
'status' => $event->status,
'jobs' => $event->jobsProcessed,
'memory' => $event->memoryUsage,
]);
});
Use it to count exits per reason for a dashboard or push each one into a metrics backend.
Key Takeaways
queue:worknow prints a stop-reason line in both plain and--jsonoutput as of Laravel 13.30.- Nine stop reasons are defined on the
WorkerStopReasonenum; each has a stable backing value and a newdescription()method. - The
reasonfield in JSON output is safe to match on in scripts and log queries. - Workers killed by
SIGKILLor an OOM killer remain silent—the event never fires in those cases. - The
WorkerStoppingevent is still the best integration point for dashboards and metrics.
Source: Laravel queue:work Now Prints Why the Worker Stopped — Laravel News