Cron Tick Hooks
The hooks over work nobody is watching: run starts, task registration, the queue, periodic rounds and worker management.
Overview
Scheduled work runs in a tick: the system wakes each minute, takes jobs off the queue, opens helper workers where needed, and finishes.
One thing is true of every hook here: nobody is watching. An error you throw is shown to no one and output you print reaches no one; the only trace is in the records.
Reference
Registering a scheduled task of your own
Runs while the task registry is gathered. This is where you register your own.
CronJobQueue::register(), not by returning.Hook::add('register:cronjobs', 10, function () {
// You register with a call, not with a return.
CronJobQueue::register('acme.sync', AcmeSyncHandler::class);
});Stopping a tick from running
Runs before a tick starts. Stopping it means no job is processed in that tick.
Hook::add('gate:cron.worker.run', 10, function ($isCli, $sapi) {
// No task runs inside a maintenance window.
if (Acme::maintenanceWindow()) return 'maintenance window';
return null;
});Following a tick starting
Runs where a tick starts.
Hook::add('action:cron.tick.started', 10, function ($workerId, $isChild, $startTs) {
// Helper workers run this hook too: tell the two apart.
if ($isChild) return;
Ops::heartbeat($workerId, $startTs);
});Following a tick finishing
Runs after a tick finished.
status (ok or partial), the worker id, how many jobs ran, the duration, the errors, plus scheduler and helper details. partial means the tick did not finish its work.Hook::add('action:cron.tick.completed', 10, function ($payload, $isChild) {
// partial means the tick ran out of room; a run of them is a capacity problem.
if (($payload['status'] ?? '') === 'partial')
Ops::warn('cron-partial', (int) ($payload['processed'] ?? 0));
});Following a processed job
Runs after a queued job was processed.
Hook::add('action:cron.job.processed', 10, function ($job, $workerId) {
// An attempt count above one means this job failed before.
if ((int) ($job['attempts'] ?? 0) > 1)
Ops::note('cron-retry', $job['type'] ?? '', (int) $job['attempts']);
});Joining the daily work
Runs in the daily task round. Unlike the other hooks your return is recorded.
Hook::add('action:cron.day.run', 10, function () {
// Your return shows in the panel: true on success, a note otherwise.
$done = Acme::nightlyReport();
return $done ? true : ['status' => false, 'message' => 'the report failed'];
});Joining the hourly work
Runs once an hour. Attach hourly work here instead of opening a task file: cache clearing, summaries, outside syncing.
Hook::add('action:cron.hour.run', 10, function () {
$n = Acme::syncPartnerCatalog();
return ['status' => true, 'message' => $n . ' records synced'];
});Joining the per-minute work
Runs every minute, the tightest of the rounds. It suits queue draining, outside polling and health checks.
Hook::add('action:cron.minute.run', 10, function () {
// Keep it short: this block runs every minute.
$sent = Acme::drainOutbox(50);
return ['status' => true, 'message' => $sent . ' messages sent'];
});Joining the monthly work
Runs once a month: month-end summaries, reconciliation, archiving.
Hook::add('action:cron.month.run', 10, function () {
$rows = Acme::archiveLastMonth();
return ['status' => true, 'message' => $rows . ' rows archived'];
});Changing how many helpers open
Runs after it was decided how many helper workers to open under load.
count (how many to open), pending jobs, active workers, the threshold, the ceiling, the reason. The field that acts is count; the rest explain the decision.Hook::add('filter:cron.spawn.count', 10, function (&$decision) {
// Only count acts; open no helpers while the server is under load.
if (Acme::loadHigh()) $decision['count'] = 0;
});Changing the chart task list
Runs while the task picker of the activity chart is being prepared on the automation board. You can add a task type of your own or hide the internal ones.
Hook::add('filter:cron.dashboard_chart_tasks', 10, function (&$options) {
// Add a task of your own to the list.
$options['acme-sync'] = 'Acme sync';
// Hide an internal one.
unset($options['queue-cleanup']);
});Stopping a run by hand
Runs before an operator runs a task by hand.
Hook::add('gate:cron.task_run_now', 10, function ($task, $user_id) {
// Triggered from the panel: a person reads your message.
if ($task === 'invoice.generate' && Acme::billingFrozen())
return 'Billing is frozen; this task cannot run now.';
return null;
});Following the kill switch
Runs where all scheduled work is switched on or off.
Hook::add('action:cron.kill_switch_toggled', 10, function ($enabled, $user_id) {
// Switching off stops every automation: forgotten, not even invoices go out.
if (!$enabled) Ops::alert('cron-disabled', (int) $user_id);
});Pitfalls
On scheduled work hooks nobody is watching. An error you throw is shown to neither customer nor operator and only lands in the records. To make a problem known, do it yourself: a notification, a record, an alert.
Under load the system opens helper workers and each one runs the tick hooks. A notification on "tick started" sends dozens in a busy minute. Read the helper mark in the second parameter.
A failed job is tried again and the processed hook runs on each attempt. A listener reacting without reading the attempt count produces a run of records or messages for one job. Check the counter in your first line.
Unlike other event hooks, the periodic ones gather your return and show it in the panel as success, duration and errors. Returning nothing leaves your entry blank. Return true on success and an array with a note on failure.
Related Articles
- Scheduled Task Hooks
- Service Status Hooks
- Invoice Lifecycle Hooks
Merci pour votre retour !
Notre équipe d'assistance est disponible 24h/24 pour tout ce que vous ne trouvez pas ci-dessus.