Notifications
A pipeline says when it notifies in its notify block, and through which channel. Channels are declared once, in the data repo's pipelines.yaml, and belong to the data repo, not the engine. The only channel type today is shell: a channel is a command, usually a script in scripts/, and it does whatever that script does.
The notify block
notify: on: [success, failed] stale_after: 72h channel: email| Field | Type | Notes |
|---|---|---|
on | list of success, failed, skipped | Every path that closes a run notifies, including a skipped guard and a busy lock, so skipped really does fire. Beware of noise: a pipeline with throttle skips half the nights by design. For "it isn't running", stale_after is the better tool. |
stale_after | duration (72h) | Optional. Notifies when the pipeline has gone that long without a single successful run, whatever the status of the current run. A pipeline that has never succeeded counts as stale from its first run. It warns on every run while that holds, and switches itself off as soon as the pipeline runs green again. |
channel | string | Must exist as a key under channels: in pipelines.yaml. validate checks it. |
Why stale_after matters
on: can't tell a routine skip from a stalled pipeline. In practice a pipeline can spend many nights skipping because of a broken guard without anyone noticing. Declaring a stale_after window somewhat larger than the pipeline's throttle turns that silence into a warning.
The linter makes it mandatory: a pipeline with triggers: and notify: but without notify.stale_after fails validate (rule R4, see Surviving the night).
stale_after can only speak when a run happens. A job that has stopped firing altogether produces no run to evaluate; for that, check the dashboard's overview.
Declaring a channel
In pipelines.yaml, at the root of the data repo:
channels: email: type: shell env: [SMTP_PASSWORD] run: scripts/notify-email.sh| Channel field | Type | Required | Notes |
|---|---|---|---|
type | "shell" | yes | The only type supported today. |
run | string | yes | The command to run. |
env | string list | no | Names of the secrets this channel needs. They are resolved only when the channel is actually invoked, and never exposed to the pipeline that triggered the notification. |
What the script receives
The channel's command runs with these environment variables, plus the secrets declared in its env::
| Variable | Value |
|---|---|
PIPELINES_NOTIFY_NAME | The pipeline's name. |
PIPELINES_NOTIFY_RUN_ID | The run id. |
PIPELINES_NOTIFY_STATUS | The run's status. |
PIPELINES_NOTIFY_REASON | The guard that skipped the run or, if the run failed, the failed steps with their error, truncated to 500 characters. |
PIPELINES_NOTIFY_STALE | 1 or empty. |
PIPELINES_NOTIFY_LAST_SUCCESS | ISO timestamp of the last successful run, empty if there was none. |
Like a shell step, a channel gets a narrow, fixed environment, never the whole environment of the process. It never receives your Anthropic API key.
After the run, pipelines show <name> <run-id> includes a notify entry with the channel, its exit code, the timestamp and the redacted tail of its output, also when the channel failed.
On a server
A server has no desktop notifications, so use a channel that goes out over the network: for example, a script that posts to ntfy with the topic in the data repo's .env (NTFY_TOPIC=a-long-hard-to-guess-name). The topic works like a secret: don't put it in the YAML or in a repo.
The starter's channels
pipelines-starter comes with three channels in its pipelines.yaml:
default, what every starter pipeline uses: a macOS notification on a Mac, and a push through ntfy on any other machine. On a Mac it works with no setup (the first time, macOS may ask you to allow notifications for Script Editor). Elsewhere it needsNTFY_TOPICin.env; without it, the notification fails and says so.macos: always a macOS notification with the first line of the report.ntfy: a push notification on your phone with the full report. Install the ntfy app, subscribe to a long, hard-to-guess topic name, put it in.envasNTFY_TOPIC=..., and changechannel: defaulttochannel: ntfyin the pipelines you want on your phone (also from a Mac).