NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
Packagist · #3550 most downloaded on Packagist
The Queue plugin for CakePHP provides deferred task execution.
Last release today
07 Oct 2026
Release timing varies
gaps range from 8 days to 4 months
Rarely documented
notes for 11 of the last 60 stable releases
Nothing withdrawn
no release was ever pulled
12 years old
137 releases · first in 2014
Queued mails were sent with an empty body: EmailTask set the bodies from the payload, then Mailer::deliver('') re-rendered over them. SimpleQueueTrans
EmailTask set the bodies from the payload, then Mailer::deliver('') re-rendered over them. SimpleQueueTransport payloads also lost headers, attachments and the configured transport (#515).queue job reset all) re-queued running jobs, so a second worker ran them again. Only failed and aborted jobs are reset now (#515).createJob() with unique no longer dedupes against an aborted job (#515).Queue.workerPhpTimeout is honored. queue worker kill works in multiserver mode, and queue worker end all handles several workers sharing PID 1 (#515)./\host, and job edits are limited to notbefore and priority (#515).config/app.example.php referenced the removed Cake\Mailer\Email class (#515).Queue.adminDetailsLimit and is hidden while no job is aborted. New on QueuedJobsTable: getAbortedStats() and getAbortedCount() (#514).Queue.cleanupAbortedTimeout lets the cleanup remove aborted jobs (default 0 keeps them), queue job flush --aborted deletes them, and the dashboard card has a "Reset All" button. queue info shows the aborted count, and queue info --format json prints job counts, workers and statistics for monitoring (#516).Full Changelog: 8.15.1...8.16.0
One column per quarter.
Truncate failure_message and captured output to the column size before saving. A failure larger than the TEXT column (e.g. a DB error echoing a huge q
failure_message and captured output to the column size before saving. A failure larger than the TEXT column (e.g. a DB error echoing a huge query) made the save throw, crashing the worker while recording the failure - the job got stuck and the queue stalled.Full Changelog: 8.15.0...8.15.1
Persist a terminal aborted status so dead jobs stop counting as queued. Once a job exhausted its retries it kept completed IS NULL forever, so isQueue
aborted status so dead jobs stop counting as queued. Once a job exhausted its retries it kept completed IS NULL forever, so isQueued() and the admin "in_progress" filter still counted it — wedging non-concurrent callers (e.g. a QueueScheduler row) behind the dead job and rendering it as "Running". The Processor now stamps QueuedJobsTable::STATUS_ABORTED (via the new markJobAborted()) when getFailedStatus() is terminal; isQueued() and the in_progress filter exclude aborted rows, and the status badge renders them as "Failed". Retryable failures are untouched and null/other statuses are unaffected, so the change is backward compatible (#504).createJob() now targets dereuromark/cakephp-dto v3, whose FromArrayToArrayInterface moved to the PhpCollective\Dto\Dto namespace. The interface check is guarded by interface_exists() and still falls back to any object exposing toArray(), so plain arrays and non-DTO objects are unaffected. The dev requirement moves to ^3.0.0 and a conflict with dereuromark/cakephp-dto <3.0.0 is declared, so the plugin can no longer be paired with the old DTO major (#502).Full Changelog: 8.14.0...8.15.0
QueueProcessesTable::add() now evicts stale (pid, server) rows whose heartbeat is older than the new Queue.staleHeartbeatThreshold (default 90s) befor
QueueProcessesTable::add() now evicts stale (pid, server) rows whose heartbeat is older than the new Queue.staleHeartbeatThreshold (default 90s) before inserting, fixing the worker crash-loop on containerized deployments (Docker/FrankenPHP). When the killed worker's row survived and the new worker tried to register with the same recycled PID, the unique-index collision threw a QueryException that the existing PersistenceFailedException catch in Processor::run() didn't handle (#493).--verbose is set, so a worker waiting for jobs no longer floods stdout/log with "Looking for Job …" / "nothing to do, sleeping." lines on every loop iteration (#491).Processor::captureOutput() no longer caches the schema check for the lifetime of the worker. A long-running worker started before bin/cake migrations migrate added the output column to queued_jobs would see the cached false for the rest of its runtime and silently drop captured stdout until restart. Explicit Queue.captureOutput config is still memoised (that branch never changes mid-process); auto-detect now re-checks per call (#496).ExecuteTask::add() no longer mangles quoted command paths with embedded spaces. explode(' ', ...) split "/usr/local/bin/My Tool" arg1 across command and params[0]; switched to str_getcsv with space delimiter so quoted paths survive intact. The plain cmd arg1 arg2 shape continues to work unchanged (#496).QueueController::index() caps the rendered pending/scheduled lists at Queue.adminDetailsLimit (default 200) to avoid OOM on realistic backlogs. With thousands of pending or failed-and-retried rows the dashboard previously crashed via DebugKit's Variables panel serialising every view variable. Aggregate counts on the tiles keep reflecting the true totals; truncated lists get a "Showing N most recent of M" notice with a pre-filtered link to the QueuedJobs admin (#497).New --force (-f) option on bin/cake queue worker clean wipes every queue_processes row regardless of the heartbeat threshold (#492). Recovery path for the same container-restart scenario above when an operator wants a one-shot manual reset.
Queue.adminBackUrl (with optional Queue.adminBackLabel) makes the admin layout's "Back to App" header link configurable, so installations can point users back to a non-default app URL. Mirrors the same pattern used by cakephp-audit-stash, cakephp-bouncer and cakephp-databaselog.
workerkey is now the canonical worker identity in queue_processes (#494). The unique (pid, server) index has been dropped (re-added as a non-unique index for query performance) because PID is not a stable identity — the OS recycles low PIDs across container restarts. update(), remove(), and endProcess() look up by workerkey (already uniquely indexed); when multiple rows share a PID, the most recently heartbeated row is targeted. Migration required: run bin/cake migrations migrate -p Queue.
createJob() gains an opt-in unique option that dedups fan-out enqueues against the existing reference-keyed pending job (#498). First call inserts as usual; subsequent calls while the original is still pending return the existing entity without inserting. Once it completes, the next call inserts a fresh row, so scheduled re-runs continue to enqueue normally. Requires reference to be set (throws InvalidArgumentException otherwise); default is false so existing callers are unchanged.
$queuedJobsTable->createJob(
'VolunteerCheckOutReminder',
['account_uuid' => $accountUuid],
[
'reference' => 'volunteer_check_out:' . $accountUuid,
'unique' => true,
],
);Admin dashboard banner gains a red Queue Stalled state for "action required" signaling (#499). Triggers when the last heartbeat is older than Queue.dashboardStalledAfter (default 120s) with a pending backlog and no in-flight job, or when no worker has reported at all and there's a pending backlog or a stuck fetched job. The existing green Queue Running and yellow Queue Idle states are unchanged. When red, the banner expands with a diagnostic grid (last activity, workers/servers, pending count) and a one-line cause hint. Two new config keys: Queue.dashboardIdleAfter (60s default), Queue.dashboardStalledAfter (120s default).
Full Changelog: 8.13.0...8.14.0
Queue.executeAllowedCommands is now required when debug=false for any deployment that runs Queue.Execute jobs ( #485 ). With the key unset or empty in
Queue.executeAllowedCommands is now required when debug=false for any deployment that runs Queue.Execute jobs (#485). With the key unset or empty in production, every Execute job is rejected before exec() is invoked. Migration — add to config/app.php (or app_local.php):
'Queue' => [
'executeAllowedCommands' => [
'bin/cake',
// '/usr/bin/php',
],
],In dev (debug=true) the allow-list is ignored, so local environments are unaffected. See config/app.example.php and docs/sections/tasks/execute.md.
ExecuteTask now escapes the command and each params entry per token via escapeshellarg() instead of escapeshellcmd() (#485). Each value is wrapped as a single shell argument, which closes argument-injection paths but means callers that previously packed multiple tokens into a single entry must split them across the array. Migration:
// before
['command' => 'bin/cake importer run', 'params' => ['--limit 10']]
// after
['command' => 'bin/cake', 'params' => ['importer', 'run', '--limit', '10']]See docs/sections/upgrading.md for the full note.
EmailTask::run() now restricts unserialize() to the configured Message subclass via allowed_classes, closing a gadget-chain risk on legacy raw-serialized settings (#484). Modern array-path callers using EmailTask::serialize() are unaffected.escapeTitle instead of the broader escape so URL/class/title attributes stay HTML-escaped while the title text can still carry icon markup (#483).Full Changelog: 8.12.0...8.13.0
Queue.adminAccess is now required to access the admin backend at /admin/queue/... . The host application MUST set it to a Closure that returns literal
Queue.adminAccess is now required to access the admin backend at /admin/queue/.... The host application MUST set it to a Closure that returns literal true to grant access — anything else (unset, non-Closure, returns false, returns a truthy non-bool, throws) yields a 403. The admin UI can trigger jobs (via AddFromBackendInterface tasks), reset / remove queued jobs, and terminate workers; accidental exposure is harmful, so the default policy is now deny.
Migration — add to config/bootstrap.php:
use Cake\Core\Configure;
use Cake\Http\ServerRequest;
Configure::write('Queue.adminAccess', function (ServerRequest $request): bool {
$identity = $request->getAttribute('identity');
return $identity !== null && in_array('admin', (array)$identity->roles, true);
});See docs/sections/admin_dashboard.md for the Authorization subsection. adminAccess is independent of Queue.standalone — the gate runs in both modes.
queue_processes rows on worker startup (#480) — workers now clear orphan rows on launch instead of leaving them around for the timeout sweep.Full Changelog: 8.11.0...8.12.0
Fix Queue::addJob() silently no-op'ing when a task's run() did not actually queue a job. The return value now reliably reflects whether a job was crea
Queue::addJob() silently no-op'ing when a task's run() did not actually queue a job. The return value now reliably reflects whether a job was created. (#478)Queue.workerLifetimeJitter config option to stagger worker shutdowns and avoid thundering-herd restarts when many workers are spawned together. (#476)<script> blocks now use nonces and inline onclick handlers were removed. (#477)style attribute to a CSS class so the admin UI works under strict CSP. (#479)Full Changelog: 8.10.2...8.11.0
Fix EmailTask calling undefined methods on Mailer / Message when queued payloads contained keys from a serialized Cake\Mailer\Message ( htmlMessage ,
EmailTask calling undefined methods on Mailer/Message when queued payloads contained keys from a serialized Cake\Mailer\Message (htmlMessage, textMessage, appCharset). These are now routed to the correct Message setters (setBodyHtml, setBodyText, setCharset fallback). (#474, fixes #473)EmailTask throwing Unknown named parameter when settings.headers or the readReceipt address setting was an associative map. (#474)Full Changelog: 8.10.1...8.10.2
Fix EmailTask throwing Unknown named parameter on PHP 8+ when queued payloads used CakePHP's associative ['email' => 'Name'] address-map format for to
EmailTask throwing Unknown named parameter on PHP 8+ when queued payloads used CakePHP's associative ['email' => 'Name'] address-map format for to, from, cc, bcc, or replyTo. Previously-working payload formats (string, ['email', 'Name'] tuple, legacy wrapped [['email' => 'Name']]) are unchanged. (#472, fixes #471)Full Changelog: 8.10.0...8.10.1
Modern Isolated Admin Dashboard
This release introduces a modern, self-contained admin dashboard for the Queue plugin that is completely isolated from the host application's CSS/JS.
QueueAppController) that extends Cake\Controller\Controller directly'Queue' => [
// Layout for admin pages:
// - null (default): Uses 'Queue.queue' isolated Bootstrap 5 layout
// - false: Disables plugin layout, uses app's default layout
// - string: Uses specified layout
'adminLayout' => null,
// Auto-refresh dashboard every N seconds (0 = disabled)
'dashboardAutoRefresh' => 30,
],Full Changelog: 8.9.1...8.10.0
Fixed hidden dependency on Tools plugin in admin templates by adding runtime fallbacks for relLengthOfTime() (falls back to CakePHP's timeAgoInWords()
relLengthOfTime() (falls back to CakePHP's timeAgoInWords()) and Format->ok() (uses element with fallback)JsonableBehavior by adding local ArrayType classFull Changelog: 8.9.0...8.9.1
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Your coding agent can read these notes before it upgrades. Set up the MCP server →