---
title: "Running checks — Alerts for Laravel | Roundly"
description: "How the scheduler, Health::runDue() and the queued job run due checks — and how Health::for($owner)->run() runs one synchronously."
url: https://roundly-consulting.com/open-source/docs/alerts-for-laravel/running-checks
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Alerts for Laravel](https://roundly-consulting.com/open-source/docs/alerts-for-laravel.md)

# Running checks

When schedule.enabled is true (the default), the package registers alerts:perform-health-checks on Laravel’s scheduler at the schedule.frequency method, with onOneServer() — several scheduler hosts queue each due check once, given a shared cache store — and a five-minute withoutOverlapping(5) lock, so a scheduler killed mid-run blocks monitoring for minutes, not a day. Whenever history is enabled it also schedules alerts:prune-runs daily, whatever schedule.enabled says. To wire the perform command yourself, disable that flag and add it to routes/console.php — the daily prune stays scheduled:

```php
use Illuminate\Support\Facades\Schedule;

Schedule::command('alerts:perform-health-checks')->everyMinute();
```

```bash
php artisan alerts:perform-health-checks
```

The command calls Health::runDue(), which walks every scheduled row, dispatches the configured job (HealthCheckJob by default) for each row whose cron came due at any minute since the previous call — once, however many of its minutes fell in the gap; on a first call, the current minute only — and returns how many it queued. The command prints that count. Soft-deleted and on-demand rows are skipped. A row whose cron cannot be evaluated (written by a seeder or by hand, since save() validates) is skipped and reported to your exception handler, and the rows after it are still dispatched. Call it yourself from anywhere:

```php
use RoundlyConsulting\Alerts\Facades\Health;

$queued = Health::runDue(); // int — what alerts:perform-health-checks calls
```

runDue() keeps the minute of each call in your default cache store, so a schedule.frequency coarser than a row’s cron never steps over it — everyFiveMinutes runs a 7 \* \* \* \* check at :10, everyOddHour runs an @daily one at 01:00 — and a scheduler that was down catches up with one run per check that came due meanwhile.

## Scheduler and queue

HealthCheckJob is a queued job, so both Laravel’s scheduler and a queue worker must be running. With the sync queue driver, checks run inline.

```bash
# Laravel's scheduler must run for scheduled checks to fire
* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1

# Locally: php artisan schedule:work
# And a queue worker to execute the queued HealthCheckJob:
php artisan queue:work
```

## What one run does

Every run — queued or synchronous — goes through one internal pipeline, RunHealthCheckAction. Options are read from the persisted row, so they survive queue serialization:

- Run check() under the row’s timeout and an exception guard, timing it.
- Record a HealthCheckRun (when history is enabled).
- A skipped result stops here — no counters, no alert side effects.
- Update consecutive\_failures or consecutive\_successes. Increments are atomic, so overlapping runs never lose a count.
- Check for an active mute on the check key, its tags or \*.
- On failure: an open alert first follows the result — its status, message and muted flag. Once failAfter() is reached, open an alert (or reuse the open one), dispatch HealthCheckFailed, then notify or escalate. A check has at most one open alert, even when two runs overlap.
- On success: once recoverAfter() is reached, close the open alert, reset its escalation level and dispatch HealthCheckRecovered — once, even when two runs overlap.

## Run a check now

Run a check synchronously against an owner. It performs the same alert, notify and recover side effects as the queued job and returns the CheckResult. A class name runs the instance you registered for that class, with its configuration; an instance runs that instance. Neither changes the registered check:

```php
use RoundlyConsulting\Alerts\Facades\Health;

$result = Health::for($team)->run(DiskUsageCheck::class);
$result->status; // Status::Ok | Warning | Failed | Skipped

// Run one of the team's scheduled rows as-is (another owner's row is refused):
Health::for($team)->run($team->healthChecks()->first());

// An inline check has no class — run it by its registered key:
Health::for($team)->run('redis-up');
```

Running a check now never starts monitoring it. When the owner already has a scheduled row for the check, the run goes through that row and shares its counters, history and alerts. Otherwise the package seeds an on-demand row (frequency is null) that keeps the owner’s counters, history and alerts for that check between manual runs. The scheduler never queues it and monitors() does not list it — to monitor a check on a schedule, use monitor().

Given one of the owner’s rows, run() runs that row as-is; a row that belongs to another owner throws InvalidHealthCheck. A class registered under several keys with as() is ambiguous by class name — run it by key.

## Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

[More ways to support, including crypto](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/alerts-for-laravel.md)
