# Task Management Hooks

https://dev.wisecp.com/es/task-management-hooks

The eight hooks where an operator reaches into scheduled work by hand: running a task, switching one off, cleaning the queue and the kill switch.

## Overview

Unlike the hooks in the previous article these fire **from the panel**: an operator is there and reads your message.

Two of them weigh heavily. The kill switch turns **every automation** off, and the queue cleanup **deletes history** so you can no longer see what ran when.

## Reference

### Following a run by hand

actioncron.task_run_now

`AdminAutomation` the job was queued

Runs after an operator triggered a task by hand. The task is **on the queue** at this point, not running yet.

Parameters 2

$taskstringThe name of the task triggered.

$job_idintThe id of the queued job. For the outcome, listen to the queue hook; this one says only that it **joined the queue**.

Return 1

voidThe return is ignored.

Listener PHP

```php
Hook::add('action:cron.task_run_now', 10, function ($task, $job_id) {
    // The job JOINED the queue and has not run: watch job.processed for the outcome.
    Audit::manualRun($task, (int) $job_id);
});
```

### Following a task switched on or off

actioncron.task_status_changed

`AdminAutomation` a single task

Runs after a single task was switched on or off.

Parameters 2

$taskstringThe name of the task whose status changed.

$enabledboolThe new state. A task switched off **stops quietly** and may go unnoticed.

Return 1

voidThe return is ignored.

Listener PHP

```php
Hook::add('action:cron.task_status_changed', 10, function ($task, $enabled) {
    // A task switched off stops quietly: record the critical ones.
    if (!$enabled && Acme::criticalTask($task)) Ops::alert('task-off', $task);
});
```

### Blocking the kill switch

gatecron.kill_switch_disable

`AdminAutomation` it stops everything

Runs before all automation is switched off. This hook fires **on the off direction only**.

Parameters 2

$enabledboolThe target state. On this hook it is **always false**: it fires when switching off, never on.

$pending_countintHow many jobs are waiting. A high number means switching off **holds them all**; weigh that before deciding.

Return 1

stringA non-empty string **stops** the action; the text is shown to the operator as the error.

Listener PHP

```php
Hook::add('gate:cron.kill_switch_disable', 10, function ($enabled, $pending_count) {
    // Switching off with many jobs waiting only grows the backlog.
    if ($pending_count > 500)
        return 'Too many jobs are waiting; let the queue drain first.';

    return null;
});
```

### Stopping a queue cleanup

gatecron.cleanup_run

`AdminAutomation` it deletes history

Runs before the queue history is deleted. The retention days are **separate per status**.

Parameters 3

$completed_daysintRetention days for completed jobs.

$cancelled_daysintRetention days for cancelled jobs.

$failed_daysintRetention days for failed jobs. Keeping this low deletes the **evidence** of a problem.

Return 1

stringA non-empty string **stops** the action; the text is shown to the operator as the error.

Listener PHP

```php
Hook::add('gate:cron.cleanup_run', 10,
    function ($completed_days, $cancelled_days, $failed_days) {
        // Deleting failures early destroys the evidence of a problem.
        if ($failed_days < 30) return 'Failed records want keeping for 30 days.';

        return null;
    });
```

### Following what a cleanup removed

actioncron.queue_cleaned

`AdminAutomation` a breakdown by status

Runs after the queue cleanup finished.

Parameters 2

$deletedintHow many records went in total.

$breakdownarrayThe breakdown by status: completed, cancelled, failed. A high failed count means what went was **the trace of a problem**.

Return 1

voidThe return is ignored.

Listener PHP

```php
Hook::add('action:cron.queue_cleaned', 10, function ($deleted, $breakdown) {
    // Many failed records removed means a problem's trace has gone.
    if ((int) ($breakdown['failed'] ?? 0) > 100)
        Ops::warn('cron-failures-purged', (int) $breakdown['failed']);
});
```

### Following a job intervention

actioncron.job_intervened

`AdminAutomation` four separate actions

Runs after an operator reached into a queued job by hand.

Parameters 3

$actionstringThe kind: `retry`, `cancel`, `force_reclaim`, `delete`. The four differ greatly; do not react without reading which.

$queuestringThe queue key: `cron`, `module`, `notification`. There are three queues and all pass this hook.

$idintThe id of the job that was touched.

Return 1

voidThe return is ignored.

Listener PHP

```php
Hook::add('action:cron.job_intervened', 10, function ($action, $queue, $id) {
    // force_reclaim takes a stuck job back, which means it runs again.
    if ($action === 'force_reclaim') Ops::note('job-reclaimed', $queue, (int) $id);
});
```

### Catching a return after downtime

actioncron.restore.detected

`cron.php` a long gap

Runs where the system noticed the tasks had not run for a long while.

Parameters 2

$restoreInfoarrayThe gap details: `gap_hours`, `gap_seconds`, `last_seen`. A long gap means the **renewals, suspensions and invoices** of that period are late.

$workerIdstringThe id of the worker that noticed.

Return 1

voidThe return is ignored.

Listener PHP

```php
Hook::add('action:cron.restore.detected', 10, function ($restoreInfo, $workerId) {
    // Renewals and suspensions are late across the gap: check the backlog.
    Ops::alert('cron-gap', (float) ($restoreInfo['gap_hours'] ?? 0));
});
```

### Changing the task list

filtercron.task_overview

`AdminAutomation` the panel cards

Runs before the task overview is shown in the panel.

Parameters 1

$tasksarrayrefThe task card rows. Adding your own here **shows it in the list** without running it: use the registration hook for that.

Return 1

voidThe value changes **by reference**; the return is not read.

Listener PHP

```php
Hook::add('filter:cron.task_overview', 10, function (&$tasks) {
    // Adding to the list does NOT run a task; registration is register:cronjobs.
    $tasks = array_filter($tasks, fn ($t) => !Acme::hidden($t['key'] ?? ''));
});
```

## Pitfalls

> **Running by hand is not running**
> 
> The manual trigger hook hands you a **queue id**: the job joined the queue and has not run. Waiting for an outcome here is pointless; what the job actually did is told by the **processed hook**.

> **A cleanup cannot be undone**
> 
> The cleanup also removes **failed** records, and those are the only trace of a problem. Shortening retention leaves tomorrow's "why did it not run?" **unanswerable**. Guard the failed retention at the gate.

> **The intervention hook carries four different actions**
> 
> Retrying, cancelling, reclaiming and deleting pass **one hook** and their outcomes are opposites. A listener written without reading the kind can take a cancelled job for a **restarted** one. There are also three queues; the second parameter says which you are in.

> **Adding to the list does not run a task**
> 
> The task list filter changes **what shows in the panel** and nothing more. Your own task does not run for being added there; it wants making known through the **registration hook**. Mixing the two leaves a card that sits in the panel and never runs.

## Related Articles

- [Cron Tick Hooks](https://dev.wisecp.com/en/cron-tick-hooks)
- Scheduled Task Hooks
- [Hooks in the Management Panel](https://dev.wisecp.com/en/hooks-in-the-management-panel)
