Releases: dereuromark/cakephp-queue
8.14.0
Fixes
QueueProcessesTable::add()now evicts stale(pid, server)rows whose heartbeat is older than the newQueue.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 aQueryExceptionthat the existingPersistenceFailedExceptioncatch inProcessor::run()didn't handle (#493).- Idle-poll heartbeat output is now suppressed unless
--verboseis 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 beforebin/cake migrations migrateadded theoutputcolumn toqueued_jobswould see the cachedfalsefor the rest of its runtime and silently drop captured stdout until restart. ExplicitQueue.captureOutputconfig 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" arg1acrosscommandandparams[0]; switched tostr_getcsvwith space delimiter so quoted paths survive intact. The plaincmd arg1 arg2shape continues to work unchanged (#496).QueueController::index()caps the rendered pending/scheduled lists atQueue.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).
Improvements
-
New
--force(-f) option onbin/cake queue worker cleanwipes everyqueue_processesrow 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 optionalQueue.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. -
workerkeyis now the canonical worker identity inqueue_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(), andendProcess()look up byworkerkey(already uniquely indexed); when multiple rows share a PID, the most recently heartbeated row is targeted. Migration required: runbin/cake migrations migrate -p Queue. -
createJob()gains an opt-inuniqueoption that dedups fan-out enqueues against the existingreference-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. Requiresreferenceto be set (throwsInvalidArgumentExceptionotherwise); default isfalseso 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
8.13.0
⚠️ Breaking Changes
-
Queue.executeAllowedCommandsis now required whendebug=falsefor any deployment that runsQueue.Executejobs (#485). With the key unset or empty in production, every Execute job is rejected beforeexec()is invoked. Migration — add toconfig/app.php(orapp_local.php):'Queue' => [ 'executeAllowedCommands' => [ 'bin/cake', // '/usr/bin/php', ], ],
In dev (
debug=true) the allow-list is ignored, so local environments are unaffected. Seeconfig/app.example.phpanddocs/sections/tasks/execute.md. -
ExecuteTask now escapes the
commandand eachparamsentry per token viaescapeshellarg()instead ofescapeshellcmd()(#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.mdfor the full note.
Fixes
EmailTask::run()now restrictsunserialize()to the configured Message subclass viaallowed_classes, closing a gadget-chain risk on legacy raw-serialized settings (#484). Modern array-path callers usingEmailTask::serialize()are unaffected.- Pagination element now uses
escapeTitleinstead of the broaderescapeso 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
8.12.0
Security adjustment
-
Queue.adminAccessis now required to access the admin backend at/admin/queue/.... The host application MUST set it to aClosurethat returns literaltrueto grant access — anything else (unset, non-Closure, returnsfalse, returns a truthy non-bool, throws) yields a403. The admin UI can trigger jobs (viaAddFromBackendInterfacetasks), 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.mdfor the Authorization subsection.adminAccessis independent ofQueue.standalone— the gate runs in both modes.
Improvements
- Sweep stale
queue_processesrows 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
8.11.0
Fixes
- Fix
Queue::addJob()silently no-op'ing when a task'srun()did not actually queue a job. The return value now reliably reflects whether a job was created. (#478)
Improvements
- Add
Queue.workerLifetimeJitterconfig option to stagger worker shutdowns and avoid thundering-herd restarts when many workers are spawned together. (#476) - Refactor admin UI to be CSP-compatible: inline
<script>blocks now use nonces and inlineonclickhandlers were removed. (#477) - Move mobile-nav background image from an inline
styleattribute to a CSS class so the admin UI works under strict CSP. (#479)
Full Changelog: 8.10.2...8.11.0
8.10.2
Fixes
- Fix
EmailTaskcalling undefined methods onMailer/Messagewhen queued payloads contained keys from a serializedCake\Mailer\Message(htmlMessage,textMessage,appCharset). These are now routed to the correct Message setters (setBodyHtml,setBodyText,setCharsetfallback). (#474, fixes #473) - Fix
EmailTaskthrowingUnknown named parameterwhensettings.headersor thereadReceiptaddress setting was an associative map. (#474)
Full Changelog: 8.10.1...8.10.2
8.10.1
Fixes
- Fix
EmailTaskthrowingUnknown named parameteron PHP 8+ when queued payloads used CakePHP's associative['email' => 'Name']address-map format forto,from,cc,bcc, orreplyTo. 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
8.10.0
Modern Isolated Admin Dashboard (#461)
This release introduces a modern, self-contained admin dashboard for the Queue plugin that is completely isolated from the host application's CSS/JS.
Key Features
- Bootstrap 5.3.3 + Font Awesome 6.7.2 via CDN with SRI hashes
- Isolated base controller (
QueueAppController) that extendsCake\Controller\Controllerdirectly - Default enabled - the new layout is used automatically (can be disabled)
- Mobile responsive - offcanvas navigation for mobile devices
- Backwards compatible - can revert to app's layout via config
Configuration Options
'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,
],Dashboard Features
- Status banner showing queue running/idle state
- Statistics cards for pending, scheduled, running, and failed jobs
- Pending/scheduled jobs tables with inline actions
- Job statistics with Chart.js visualization
- Trigger jobs section for addable tasks
- Configuration display
- Sidebar navigation with quick actions
- Mobile offcanvas navigation
Stats Page Improvements
- Fixed Chart.js infinite vertical expansion issue
- Job type filter now uses query parameters (supports job types with slashes)
- Added back button when viewing specific job type stats
Full Changelog: 8.9.1...8.10.0

8.9.1
Fixes
- Fixed hidden dependency on Tools plugin in admin templates by adding runtime fallbacks for
relLengthOfTime()(falls back to CakePHP'stimeAgoInWords()) andFormat->ok()(uses element with fallback) - Fixed hidden dependency on Shim plugin in
JsonableBehaviorby adding localArrayTypeclass
Full Changelog: 8.9.0...8.9.1
8.9.0
8.8.0
Improvements
- varexporter v7 support
- Add Queue.Job.* lifecycle events with optional Sentry bridge
Full Changelog: 8.7.0...8.8.0