Notifications¶
Notifications are fire-and-forget messages sent during or after job execution. Unlike resources, notifications have no versioning, no check intervals, and no pull lifecycle. They are ideal for sending status updates to external services like GitHub Checks, Slack, or Discord.
Notification Types¶
A notification_type defines how to send a notification. It specifies the runner command used to deliver the message.
notification_type "slack" {
params = ["webhook_url"]
notify "exec" {
path = "/bin/sh"
args = ["-ec", "curl -sf -X POST -H 'Content-Type: application/json' \"$param_webhook_url\" -d '{\"text\": \"'\"$NOTIFY_MESSAGE\"'\"}'"]
}
}
Notification types can also be loaded from a remote source:
Parameters¶
params- List of parameter names that notifications of this type can provide.notify- A runner command block that executes the notification logic.runner- Optional runner override for the notify command. See Runners — Type-level runner overrides.
Notifications¶
A notification defines what to send and where. It references a notification type and provides concrete parameter values.
A notification without an on field is manual-only — it will never fire automatically. You must explicitly use notify steps in job plans or hooks to trigger it. This is useful for notifications like github-check where you need precise control over when each notification is sent (e.g., status = "in_progress" at the start, conclusion = "success" at the end).
notification "slack" "deploys" {
params {
webhook_url = "https://hooks.slack.com/services/T00/B00/xxx"
}
message = "Deploy finished for ${var.app_name}"
}
Fields¶
| Field | Description |
|---|---|
type |
The notification type name (first label) |
name |
A unique name for this notification (second label) |
params |
Key-value parameters passed to the notification type |
message |
Default message text (available as $NOTIFY_MESSAGE). Supports HCL variables (var.*). |
on |
List of events for automatic dispatch: success, failure, cancel, all. If omitted, the notification is manual-only. |
jobs |
Limit automatic dispatch to these job names only (requires on) |
exclude |
Exclude these job names from automatic dispatch (requires on) |
Using Notify¶
Use notify steps directly in a job plan or hooks to send notifications at specific points during execution. This is the only way to trigger notifications that don't have an on field.
notification_type "github-check" {
source = "pikoci://github-check"
}
notification "github-check" "ci" {
params {
app_id = var.github_app_id
installation_id = var.github_app_installation_id
private_key = var.github_app_pem
repository = "org/repo"
base_url = "https://ci.example.com"
}
}
job "deploy" {
get "git" "repo" { trigger = true }
notify "github-check" "ci" { status = "in_progress" }
task "build" {
run "exec" {
path = "make"
args = ["build"]
}
}
on_success {
notify "github-check" "ci" { conclusion = "success" }
}
on_failure {
notify "github-check" "ci" { conclusion = "failure" }
}
}
In this example, notification "github-check" "ci" has no on field, so it only fires when explicitly called via notify steps in the plan and hooks.
Notify Step Parameters¶
Attributes on a notify step are passed to the notification type's command with a notify_ prefix. For example, status = "in_progress" becomes the environment variable $notify_status.
The special message attribute overrides the notification's default message.
Notify steps work in all hook types (on_success, on_failure, on_cancel, ensure) and at the top level of a job plan.
Automatic Notifications¶
The on field enables automatic notification dispatch after job hooks complete, based on the final build status:
notification "slack" "all-builds" {
params {
webhook_url = var.slack_webhook
}
on = ["success", "failure"]
message = "Build finished"
}
Event Mapping¶
| Build Status | Event Name |
|---|---|
| Succeeded | success |
| Failed | failure |
| Cancelled | cancel |
Use on = ["all"] to match every event. all cannot be combined with other events.
Job Scoping¶
By default, automatic notifications fire for every job. Use jobs or exclude (mutually exclusive) to limit scope:
notification "slack" "deploy-only" {
params { webhook_url = var.slack_webhook }
on = ["success", "failure"]
jobs = ["deploy", "release"]
}
notification "slack" "skip-lint" {
params { webhook_url = var.slack_webhook }
on = ["failure"]
exclude = ["lint"]
}
github-check¶
The github-check notification type reports build status back to GitHub as check runs. It uses a GitHub App for authentication.
To use it, declare the notification type with source = "pikoci://github-check":
# Read the PEM key using the file secret type with raw format
secret_type "app-key" {
source = "pikoci://file"
format = "raw"
path = "/etc/pikoci/github-app.pem"
}
variable "github_app_key" {
type = string
secret "app-key" {
key = "content"
}
}
notification_type "github-check" {
source = "pikoci://github-check"
}
notification "github-check" "ci" {
params {
app_id = "12345"
installation_id = "67890"
private_key = var.github_app_key
repository = "org/repo"
base_url = "https://ci.example.com"
}
}
job "test" {
get "git" "repo" { trigger = true }
notify "github-check" "ci" {
status = "in_progress"
}
task "run-tests" {
run "exec" {
path = "make"
args = ["test"]
}
}
on_success {
notify "github-check" "ci" {
conclusion = "success"
}
}
on_failure {
notify "github-check" "ci" {
conclusion = "failure"
}
}
}
Notification params¶
| Param | Required | Description |
|---|---|---|
app_id |
yes | GitHub App ID |
installation_id |
yes | GitHub App installation ID |
private_key |
yes | GitHub App private key content (PEM format). Use the file secret type with format = "raw" to read from a PEM file |
repository |
yes | Repository in owner/repo format |
base_url |
no | PikoCI instance URL (e.g. https://ci.pikoci.com). When set, auto-constructs a details_url from $BUILD_* env vars if no explicit notify_details_url is provided. The constructed URL follows the pattern: {base_url}/teams/{team}/pipelines/{pipeline}/jobs/{job}/builds/{number} |
Notify step params¶
| Param | Required | Description |
|---|---|---|
status |
no | Set to in_progress to create a check run |
conclusion |
no | Set to success, failure, etc. to complete a check run |
head_sha |
no | Commit SHA (defaults to git rev-parse HEAD) |
name |
no | Check run name (defaults to pipeline/job) |
details_url |
no | URL linked from the check run |
Either status or conclusion must be set. Use status = "in_progress" first to create the check run, then conclusion in hooks to update it.
GitHub App setup¶
- Go to GitHub Settings > Developer settings > GitHub Apps > New GitHub App
- Set the app name and homepage URL
- Under Permissions, grant Checks read & write
- Uncheck Active under Webhook (no webhook needed)
- Create the app and note the App ID from the settings page
- Click Generate a private key (downloads a
.pemfile) - Copy the
.pemfile to the server (e.g./etc/pikoci/github-app.pem) - Install the app on the target repository or organization
- Note the Installation ID from the URL after installing, or via
GET /app/installations
Use the file secret type with format = "raw" to read the PEM key (see example above). Never hardcode the private key in the pipeline file.
slack¶
The slack notification type posts messages to a Slack incoming webhook. By default, messages are plain text. When base_url is provided, the default message includes a clickable link to the build.
notification_type "slack" {
source = "pikoci://slack"
}
notification "slack" "builds" {
params {
webhook_url = var.slack_webhook
base_url = "https://ci.example.com"
}
on = ["failure"]
}
Notification params¶
| Param | Required | Description |
|---|---|---|
webhook_url |
yes | Slack incoming webhook URL |
base_url |
no | PikoCI instance URL. When set, the default message includes a clickable link to the build (e.g. <url|View build>). |
Default message¶
Without base_url:
With base_url:
✅ [my-pipeline/deploy] Build #42 - success - https://ci.example.com/teams/main/pipelines/my-pipeline/jobs/deploy/builds/42
When a custom message is provided (on the notification or notify step), it is used as-is — base_url only affects the default message.
Webhook setup¶
- Go to api.slack.com/apps and create a new app (or use an existing one)
- Under Incoming Webhooks, toggle it on
- Click Add New Webhook to Workspace and select a channel
- Copy the webhook URL and pass it via a pipeline variable
Examples¶
Notify on all failures with build link:
notification "slack" "failures" {
params {
webhook_url = var.slack_webhook
base_url = "https://ci.example.com"
}
on = ["failure"]
}
Notify only for deploy jobs:
notification "slack" "deploy-status" {
params {
webhook_url = var.slack_webhook
base_url = "https://ci.example.com"
}
on = ["success", "failure"]
jobs = ["deploy"]
message = "Deploy finished"
}
Manual notify in a hook:
job "release" {
get "git" "tags" { trigger = true }
task "build" {
run "exec" {
path = "make"
args = ["release"]
}
}
on_success {
notify "slack" "builds" {
message = "Release published"
}
}
}
discord¶
The discord notification type posts messages to a Discord webhook. By default, messages are plain text. When base_url is provided, the default message includes a link to the build (Discord auto-links URLs).
notification_type "discord" {
source = "pikoci://discord"
}
notification "discord" "builds" {
params {
webhook_url = var.discord_webhook
base_url = "https://ci.example.com"
}
on = ["failure"]
}
Notification params¶
| Param | Required | Description |
|---|---|---|
webhook_url |
yes | Discord webhook URL |
base_url |
no | PikoCI instance URL. When set, the default message includes a link to the build (Discord auto-links URLs). |
Default message¶
Without base_url:
With base_url:
✅ [my-pipeline/deploy] Build #42 - success - https://ci.example.com/teams/main/pipelines/my-pipeline/jobs/deploy/builds/42
When a custom message is provided, it is used as-is — base_url only affects the default message.
Webhook setup¶
- In Discord, go to Server Settings > Integrations > Webhooks
- Click New Webhook, select a channel, and copy the webhook URL
- Pass it via a pipeline variable
Message Resolution¶
The message sent to the notification type is resolved in this order:
messageattribute on thenotifystep (highest priority)messagefield on thenotificationdefinition- Empty —
$NOTIFY_MESSAGEis not set
When $NOTIFY_MESSAGE is empty, the notification type script is responsible for providing a default. The built-in slack and discord types default to:
When triggered by automatic notifications (via the on field), the build status and a status icon are included (✅ success, ❌ failure, ⚠️ cancel). For manual notify steps without a status, the default is:
These are constructed from build metadata variables ($BUILD_PIPELINE_NAME, $BUILD_JOB_NAME, $BUILD_NUMBER) and the $notify_build_status variable (automatically set by automatic notifications).
The message field in HCL supports both pipeline variables (${var.app_name}, resolved at parse time) and runtime variable interpolation ($BUILD_NUMBER, $GET_*, $TASK_*, resolved at build time). This means you can include build metadata, resource versions, and task outputs directly in notification messages.
Literal \n sequences in the expanded message are converted to real newlines, which is useful when including multiline values from $PIKOCI_OUTPUT (see example below).
The resolved message is available as the $NOTIFY_MESSAGE environment variable.
Runtime Variable Interpolation¶
Any $VAR or ${VAR} reference in the message is expanded against the available runtime variables:
$BUILD_NUMBER,$BUILD_JOB_NAME,$BUILD_PIPELINE_NAME,$BUILD_TEAM_NAME$GET_<STEP>_<KEY>— values from get steps (e.g.$GET_MY_REPO_REF)$TASK_<STEP>_<KEY>— values exported via$PIKOCI_OUTPUTfrom task steps
job "release" {
get "git" "repo" { trigger = true }
task "build" {
run "exec" {
path = "/bin/sh"
args = ["-ec", <<-EOT
TAG=$(git describe --tags --exact-match)
BODY=$(sed -n "/^## /,/^## /{//!p;}" CHANGELOG.md)
echo "CHANGELOG=$(echo "$BODY" | sed ':a;N;$!ba;s/\n/\\n/g')" >> "$PIKOCI_OUTPUT"
EOT
]
}
}
on_success {
notify "discord" "releases" {
message = "🚀 **$GET_REPO_REF** released!\n\n$TASK_BUILD_CHANGELOG\n\n🔗 https://github.com/org/repo/releases/tag/$GET_REPO_REF"
}
}
}
To include multiline content (like a changelog), write it to $PIKOCI_OUTPUT with newlines replaced by literal \n. The notification system converts them back to real newlines automatically.
Environment Variables¶
Notify commands receive all of the following as environment variables:
$param_*— Notification-level parameters (from thenotificationblock'sparams)$notify_*— Step-level parameters (from thenotifystep's attributes)$NOTIFY_MESSAGE— Resolved message text (may be empty)$BUILD_NUMBER— Current build number$BUILD_JOB_NAME— Job name$BUILD_PIPELINE_NAME— Pipeline canonical name$BUILD_TEAM_NAME— Team canonical name$WORKDIR— Working directory (shared with get/task steps)$GET_<STEP>_<KEY>— Values from get steps (e.g.$GET_MY_REPO_REF)$TASK_<STEP>_<KEY>— Values exported via$PIKOCI_OUTPUTfrom task steps
These environment variables are available inside the notification type's notify command script. For example, you can reference $BUILD_NUMBER in your script even if it wasn't part of $NOTIFY_MESSAGE.
Migration from Resource-Based Notifications¶
If you previously used resource_type/resource/put for push-only resources like github-check, migrate to the notification system:
| Before | After |
|---|---|
resource_type "github-check" { source = "pikoci://github-check" } |
notification_type "github-check" { source = "pikoci://github-check" } |
resource "github-check" "ci" { params { ... } } |
notification "github-check" "ci" { params { ... } } |
put "github-check" "ci" { status = "in_progress" } |
notify "github-check" "ci" { status = "in_progress" } |
The $put_* environment variable prefix becomes $notify_* in notification type scripts.