Watchdogs
Overview
trigger/watchdog.ts is SABLE's independent backstop for board delivery: it
does not trust the report pipeline's own success signal (reports.md),
it verifies delivery directly against the board_runs table. trigger/smoke.ts's
sableSmoke is grouped here as the closest fit among the four pages, even
though — as noted below — it is not actually a scheduled health check.
How it works
checkKind(kind) ("morning" or "eod") runs in three steps:
- Self-test the alert channel first, before any database work. If
RESEND_API_KEYorSABLE_OPS_ALERT_EMAILisn't set, the run throws immediately. This is deliberate: a watchdog whose alert path is broken otherwise "passes" every single day right up until the one day it actually needs to alert — and only then discovers the alert channel was never configured. - Resolve the org and query
board_runsfor rows whereas_ofis today (America/New_York) andorg_idmatches, wrapped inwithAlertOnCheckFailure: if the query itself throws (e.g. Supabase is unreachable), the watchdog still fires a best-effort ops alert saying delivery status is unknown, not confirmed-missing, and then always rethrows so the Trigger run fails red. The code comments call out that this is exactly the failure mode behind a July 15–21 outage, where the check itself failing went silent before this guard existed. - Apply
watchdogVerdict. Adelivered: truerow for today and the requested kind isok. A row that exists but isn't delivered reports thedelivery_status. No row at all reports "no delivered<kind>board for<date>". Any non-okverdict triggers a Resend alert naming the reason and pointing at Trigger.dev runs,trigger_runs, andREPORT_REQUIRED_CONNECTORSas starting points.
sendOpsAlert posts directly to the Resend API (POST https://api.resend.com/emails) — it is a separate call path from the
best-effort alerting reports.md's report pipeline uses, and that
separation is intentional: the watchdog's own ability to alert must not
depend on anything in the report pipeline having worked correctly.
sableSmoke is unrelated to any of this. It opens a trigger_runs row,
records which two env vars it checked (SUPABASE_URL,
SUPABASE_SERVICE_ROLE_KEY), and marks itself complete. It has no cron, no
alerting, and no verdict logic — it exists to be triggered by hand as a
basic "is this environment wired up at all" connectivity check.
Tasks
| Task | Trigger / cron | What it does |
|---|---|---|
morningWatchdog | schedule, cron pattern 45 6 * * 1-5 (weekdays only), timezone America/New_York | Verifies a delivered "morning" board_runs row exists for today; alerts via Resend if not. Runs 45 minutes after morningBoard's own 0 6 * * 1-5 cron. |
eodWatchdog | schedule, cron pattern 15 17 * * 1-5 (weekdays only), timezone America/New_York | Same check for "eod". Runs 45 minutes after eodPacket's own 30 16 * * 1-5 cron. |
sableSmoke (smoke.ts) | event — manual/on-demand, no cron | Records a trigger_runs row and reports which env vars it checked (SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY) — a manual connectivity smoke test, not a scheduled or alerting health check. See the note below on why it's placed on this page. |
Note on smoke.ts
sableSmoke doesn't cleanly belong on any of the four pages: it has no cron,
doesn't sync a connector, doesn't touch a document or a board, and carries no
alerting. It's placed here because, conceptually, it's the closest thing to a
health check in the codebase — a task you run to confirm the Trigger
environment itself is wired up — even though it shares none of the actual
watchdog machinery (no board_runs check, no Resend alert, no verdict). Treat
it as a manual connectivity probe, not part of the automated watchdog
schedule.
What can go wrong / how to investigate
- A watchdog run fails immediately with "watchdog alert channel not
configured."
RESEND_API_KEYorSABLE_OPS_ALERT_EMAILis missing from the Trigger environment. This is a deliberate fail-red self-test, not a bug — fix the environment, don't silence the check. - A watchdog run fails with "watchdog could not verify
<kind>board." Theboard_runsquery itself threw (likely a Supabase connectivity issue).withAlertOnCheckFailureshould have already sent a best-effort "delivery status is UNKNOWN" alert before rethrowing — if no alert arrived, check whether the alert channel is also down, since a broken alert path there is swallowed on purpose so it never masks the original error. - A watchdog fires "board NOT delivered" but you believe it was sent.
Check
board_runsdirectly foras_of = <today>and the relevantkind: either no row exists (the report pipeline never got far enough to persist one, or the wrongorg_idwas resolved) or a row exists withdelivered = false(persisted but the delivery task itself failed — seereports.md's notes ondeliverPersistedReportandtaskRunErrorMessage). sableSmoke"succeeds" but something is actually broken. It only confirmsSUPABASE_URLandSUPABASE_SERVICE_ROLE_KEYare present and that atrigger_runsrow can be written — it does not call any Supabase edge function, external connector, or the report/board pipeline. A greensableSmokerun is not evidence any of those are healthy.
Where the code lives
trigger/watchdog.ts—morningWatchdog,eodWatchdog,checkKind,watchdogVerdict,withAlertOnCheckFailure,sendOpsAlert,resolveWatchdogOrgId.trigger/smoke.ts—sableSmoke.trigger/lib/supabase.ts—serviceClient,createTriggerRun/completeTriggerRun/failTriggerRun.- Supabase:
board_runs(migration0027_board_runs.sql) is the table the watchdog checks directly;trigger_runs(migration0041_trigger_runs.sql) is wheresableSmokerecords its run;orgsis used byresolveWatchdogOrgIdto scope theboard_runsquery.