1. Documentation
  2. Resources
  3. Troubleshooting
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

Troubleshooting

The failures people actually hit, and what each one means.

The page is empty

hasData is false until the first check lands. Nothing checks itself, so either the scheduler is not running or it has never completed a run.

bash
heyo-status check run

If that stores results but the page is still empty:

  • The page may be statically rendered. Next.js needs export const dynamic = "force-dynamic"; Astro needs export const prerender = false.
  • The snapshot may be cached. It expires after cache.snapshotTtlMs (default 30 s); call statusPage.api.invalidate() after an out-of-band write.
  • Every monitor may be private: true, which removes it from public snapshots entirely.

Checks never run in production

The local scheduler only exists inside heyo-status dev. Deployments use platform cron:

PlatformCheck
Cloudflaretriggers.crons in wrangler.jsonc matches the three configured expressions, and the Worker exports scheduled.
Vercelvercel.json has the three entries and CRON_SECRET is set.
Self-hostedscheduleStatusPage() is actually called, in a process that stays up.

Verify manually:

bash
curl -X POST "$BASE/admin/checks/run" -H "authorization: Bearer $HEYO_STATUS_API_TOKEN"curl -H "authorization: Bearer $CRON_SECRET" https://status.acme.com/api/cron/checks

503 The management API is disabled

No admin token. Set adminToken in the configuration or HEYO_STATUS_API_TOKEN in the environment, then redeploy. On Workers, process.env may be unavailable — set it as a Worker secret and the runtime will still find it, or pass adminToken explicitly.

401 Unauthorized

The header must be exactly Authorization: Bearer <token>. Check for a trailing newline from $(cat token.txt), a quoted value including the quotes, or a proxy stripping the header.

TLS monitoring is unavailable in this runtime

ssl and tcp monitors need node:tls and node:net:

RuntimeWorksNote
Bun 1.4.3+yes
Node 20.19+yesOlder Node lacks getBuiltinModule.
Cloudflare WorkersnoThe modules do not exist.

On Workers, use http and dns, or run certificate checks from a Node host that writes to the same database.

Cloudflare D1 is not bound

The configuration was imported outside a Worker. A standalone dev server cannot manufacture a binding — run wrangler dev, or bun run dev in a generated Cloudflare project, which builds and boots the real Worker.

This is also expected during next build or a CLI command against a Cloudflare project; the lazy adapter defers it to the request that needs the database, so the build itself still succeeds.

DATABASE_URL is required

The PostgreSQL overlay resolves its pool on the first query. Set the variable for the environment that is failing — including the one your build or migration job runs in.

heyo-status: command not found

The binary is in the project's node_modules/.bin. Run it through the package manager — bun run check, pnpm exec heyo-status check run — or through the generated scripts.

Configuration file not found

The CLI loads ./heyo-status.config.ts by default. Run it from the project root, pass --config, or set HEYO_STATUS_CONFIG.

heyo-status.config.ts must export a heyoStatus() result

Export it as statusPage, as the default, or both:

ts
export const statusPage = heyoStatus({ /* … */ });export default statusPage;

Configuration throws on import

heyoStatus() validates with a strict schema, so a misspelled key is an error rather than a silent no-op. Read the Zod path in the message — it names the exact field. Common cases:

MessageCause
Unrecognized key(s)A typo, or an option that moved.
checks.displayDays cannot exceed checks.retentionDaysThe page would read deleted history.
Composite monitor … references unknown monitor …A component name does not match a monitor.
Monitor names must be unique.Two monitors share a name.
basePath cannot be "/".Mount the API below a path segment.
database must implement the StatusDatabaseAdapter contract.A partial custom adapter.

Notifications never arrive

  1. 1

    Check the threshold

    monitor.down fires on the run where consecutive failures reach failureThreshold — not before, and not again while it stays down.

  2. 2

    Check the event filter

    A channel with events only receives those types. Remove the filter to test.

  3. 3

    Check maintenance

    An in-progress window suppresses monitor events for the monitors it names — and for every monitor when affectedMonitors is empty.

  4. 4

    Read the log

    bash
    curl -H "authorization: Bearer $HEYO_STATUS_API_TOKEN" \  "$BASE/admin/notifications/log?status=failed&limit=20"

    Channel failures are isolated and never surface as a failed check, so the log is the only place the error appears.

Email subscriptions answer 503

POST /subscribers with type: "email" needs a configured email notification channel — the subscription flow reuses its transport. Add an email() channel, or drop "email" from subscribers.methods.

target must be a public host

A visitor-supplied webhook pointed at a non-routable address. This is working as intended; see Public subscriptions.

A flash of the wrong colour mode

Render statusThemeBootstrapScript() in the document head and add suppressHydrationWarning to <html>. Without the script the page paints before the stored preference is applied.

The subscribe dialog does nothing in Astro

The island needs client:load. Without it, the page renders as inert markup and neither the dialog nor the colour-mode toggle responds.

Theme changed but the width did not

Make sure you import @heyo-sh/heyo-status/theme/status.css and that the page renders through StatusShell, which publishes data-theme — the per-theme tokens are scoped to that attribute.

Duplicate checks or duplicate alerts

More than one scheduler is running: several replicas each calling scheduleStatusPage(), or a platform cron plus a local dev against the same database. Exactly one scheduler should own a deployment.

Related

  • CLI commands
  • Configuration reference
  • Security checklist
Security< Previous

Powered by heyo

On this page

The page is emptyChecks never run in production503 The management API is disabled401 UnauthorizedTLS monitoring is unavailable in this runtimeCloudflare D1 is not boundDATABASEURL is requiredheyo-status: command not foundConfiguration file not foundheyo-status.config.ts must export a heyoStatus() resultConfiguration throws on importNotifications never arriveEmail subscriptions answer 503target must be a public hostA flash of the wrong colour modeThe subscribe dialog does nothing in AstroTheme changed but the width did notDuplicate checks or duplicate alerts