1. Documentation
  2. How it works
  3. The check run
ReadmenpmGitHub
  • Introduction
  • Quickstart
  • Configuration file
  • Monitors
  • Checks and scheduling
  • Notifications
  • Incidents and maintenance
  • Subscribers
  • Metrics and badges
  • Identity and appearance
  • Telemetry
  • The check run
  • Uptime and history
  • Automatic incidents
  • Notification delivery
  • Public subscriptions
  • StatusPage component
  • Themes
  • UI primitives
  • Colour mode
  • React Router
  • Next.js
  • Astro
  • SQLite
  • Cloudflare D1
  • PostgreSQL
  • Schema and migrations
  • Cloudflare Workers
  • Vercel
  • Self-hosting
  • API overview
  • Status endpoints
  • Incident and maintenance endpoints
  • Subscriber endpoints
  • Badges, feeds and metrics
  • Admin endpoints
  • CLI commands
  • Package exports
  • Security
  • Troubleshooting

The check run

What happens, in order, every time monitors are checked.

One function does the work — statusPage.cron.runChecks(now?) — and every trigger calls it: the local scheduler, a Cloudflare cron event, a Vercel cron route, node-cron, heyo-status check run and POST /admin/checks/run. The order of operations matters, so it is the same everywhere.

flowchart TD
A["runChecks(now)"] --> B["Run every non-composite monitor concurrently"]
B --> C["Derive composite monitors from that batch"]
C --> D["Count prior consecutive failures (one history read)"]
D --> E["Collect monitor.down / monitor.recovered / ssl.expiring"]
E --> F["Insert results into status_results"]
F --> G["Reconcile automatic incidents"]
G --> H["Invalidate the snapshot cache"]
H --> I["Dispatch all events to channels and subscribers"]

Step by step

  1. 1

    Run the checks

    Every monitor that performs I/O runs concurrently, so a run costs the slowest monitor, not their sum. Each check is wrapped so that it always returns a storable CheckResult — an exception becomes a failed result with the error message, never a thrown run.

  2. 2

    Derive composites

    Composite monitors do no I/O. They are evaluated against the results just collected, which is why their components must be monitors that actually perform a check. Results are then reordered to match configuration order, so CLI output and notifications stay stable.

  3. 3

    Count failures before writing

    Transition events are decided before the batch is persisted, using one history read for the whole batch. That is what makes monitor.down fire exactly on the run where the consecutive-failure count reaches notifications.failureThreshold, and monitor.recovered fire exactly once after a threshold-reaching outage.

  4. 4

    Persist

    Results are inserted into status_results — one row per monitor per run, with name, type, status, latency, message and timestamp.

  5. 5

    Reconcile incidents

    With incidents.enabled, failing monitors can open an automatic incident and recovering ones can progress it toward resolution. See Automatic incidents.

  6. 6

    Invalidate and dispatch

    The snapshot cache is dropped so the next render is current, then monitor and incident events are delivered to every subscribed channel. Delivery failures are isolated and logged; they never fail the run.

A single result

ts
interface CheckResult {  name: string;  type: "http" | "ssl" | "tcp" | "dns" | "composite";  status: boolean;  message: string;  latencyMs: number;  checkedAt: Date;}

HTTP checks return extra diagnostics on the in-memory result — httpStatus, responseHeaders, bodyMatch, jsonMatch — and SSL checks add expiresInDays and expiring. Those fields drive the event type and the message; the persisted row keeps the six common columns.

latencyMs is wall-clock time for the whole check, measured with performance.now() and rounded, so a soft-retried HTTP check honestly includes its retry delay.

Maintenance suppression

If maintenance.enabled is on, monitors covered by an in-progress window are filtered out before events are collected and before incidents are reconciled. They are still checked and still stored — the uptime grid should show what really happened — they simply do not page anyone.

Running it yourself

heyo-status check run

The CLI prints one line per monitor and exits non-zero when any check failed, which makes it usable as a smoke test in CI:

text
UP   Website (http) 128ms — HTTP 200DOWN API (http) 10002ms — HTTP timed out after 10000msStored 2 check result(s); 1 failed.
Checks without a scheduler never run

heyoStatus() does not start timers. Nothing is checked until a scheduler calls runChecks, which is what keeps the same configuration safe to import in a build step, a test or a CLI command.

Related

  • Monitor types and options
  • How uptime is calculated
  • Notification delivery
Telemetry< PreviousUptime and historyNext >

Powered by heyo

On this page

Step by stepA single resultMaintenance suppressionRunning it yourself