NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages
Alerts for Laravel

Timeouts & error handling

Per-check timeout

timeout(5) bounds a check at five seconds:

Health::for($team)->monitor(PaymentGatewayCheck::class)
    ->everyMinute()
    ->timeout(5)   // hard abort under CLI with ext-pcntl, post-hoc report elsewhere
    ->save();

On the CLI with ext-pcntl — queue workers included — the check is hard-aborted the moment the budget elapses, via SIGALRM. Elsewhere (web SAPI, Windows) it is a best-effort post-hoc report: the check runs to completion and is marked failed if it overran. A timed-out check flows through the normal failed-result path with a CheckTimedOut exception recorded in meta, plus timed_out_after.

Inside a queue worker the job’s own --timeout alarm is kept: a check budget longer than the time the job has left never extends it, and the worker’s alarm is re-armed after the check.

Exception-safe checks

A check’s check() never needs a try/catch. Any thrown exception is converted into a clean failed result and recorded as a normal alert and run, so a bad check never fails the queue job. An exception message can be huge — a QueryException embeds the SQL — so run and alert rows store at most CheckResult::MAX_STORED_MESSAGE_LENGTH (1000) characters; the full text stays on the returned result and in meta['exception_message']:

Health::define('flaky', function () {
    throw new RuntimeException('upstream exploded'); // becomes CheckResult::failed()
});

// Or build a result from a caught exception yourself:
CheckResult::fromException($e); // status failed, exception class + bounded trace in meta
$alert = $team->alerts()->open()->first();

$alert->message;                    // stored up to CheckResult::MAX_STORED_MESSAGE_LENGTH (1000) characters
$alert->meta['exception'];          // e.g. 'RuntimeException'
$alert->meta['exception_message'];  // 'upstream exploded' — the full text, never shortened
$alert->meta['exception_trace'];    // the first 15 stack frames
$alert->meta['timed_out_after'];    // seconds — present only for timeouts

Only check() itself is guarded. Resolving the check (an unregistered key) or the owner (no HasNotifiablesForAlerts) still throws and fails the job — those are configuration errors you want to see.

Exceptions

All live in RoundlyConsulting\Alerts\Exceptions, apart from the toolkit one:

ExceptionThrown when
CheckTimedOutA check overruns its timeout(). Caught by the run and stored as a failed result — it never escapes. Carries public key and seconds.
InvalidCronExpressionmalformed(): a schedule is saved — monitor()->save(), schedule() or the trait shortcuts — with an expression that is not a valid 5-field cron or supported alias; nothing is stored. skipped(): runDue() met a stored row it cannot evaluate (written by a seeder or by hand) — reported to your exception handler, not thrown, and the rows after it are still dispatched.
InvalidHealthCheckdoesntExtendBaseCheck(): a class that does not extend Check is registered, run or scheduled. notRegistered(): a scheduled row names, or run() is given, a key that is not registered in the running process. ambiguous(): run() is given a class registered under several keys with as() — run it by key. notScheduledFor(): Health::for($owner)->run() or unmonitor() is given a row that belongs to another owner.
InvalidNotifiableForHealthCheckThe row’s owner does not implement HasNotifiablesForAlerts, or no longer exists.
InvalidConfigurationExceptionFrom package-toolkit: a config value is invalid — a model key that is not the packaged model or a subclass of it, an unknown key_type, a switch that is not a boolean, an unknown schedule.frequency, a non-string route.uri or route.name, or a history.retention_days that is not a whole number of at least 1. The message names the 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

By donating, you agree to our donation terms.

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.