Tell a chat channel when a wave stops
Optional: hand this page to your coding agentThe steps work by hand too.Show the whole prompt
Read https://intentius.io/terragucci/guides/notify-a-chat-channel/.
Add a `notify:` block to terragucci.yml naming the secret SLACK_WEBHOOK_URL (or TEAMS_WEBHOOK_URL), run
`npx terragucci config check` and `npx terragucci init`, and open a pull request. Tell me which secret I must create.
Do not create the webhook or the secret, and never write a webhook address into any file.
Never apply, approve (a pull request review or `terragucci approve`), override a policy denial (`terragucci override`), use `--mode apply`, or merge; never touch `.chant/allowed_signers` or `chant/lifecycle`.Result
Section titled “Result”A message in your channel each time an apply job stops at a wave or the drift job finds a change. The message approves nothing; a person approves with terragucci approve from a checkout, which records the approval on chant/lifecycle. Under approval: pr-review an approving review of the pull request counts too.
| You want | You set up |
|---|---|
a notice in Slack or Teams, and under approval: pr-review a link straight to the review that approves the wave |
notify: and an incoming webhook for the channel. Nothing to install or host. |
Approve and Decline buttons that record the approval on chant/lifecycle as the person who clicked |
terragucci relay, in your own cloud (Approve from Slack and Teams) |
| The wave | Exit | The message says |
|---|---|---|
| waits for an approval | 3 | the wave, its roots, its plan digest, the terragucci approve command for that digest, and the run; under approval: pr-review, first a link to review the pull request (below) |
| is refused: its plans changed after approval | 4 | the wave, its roots, the outcome line, npx terragucci approve wave-<k> --plan <digest> for the new digest, and the run |
| fails, or the policy denies it | 1 | the wave, its roots, the outcome line, that there is nothing to approve, and the run |
Prerequisites
Section titled “Prerequisites”| You need | Why |
|---|---|
| The pipeline | init maps the secret into the apply jobs |
| An incoming webhook for the channel | the apply job posts to it |
| A secret in your forge’s store holding the webhook’s address | the address is a credential: anyone holding it can post |
-
Make a webhook for the channel.
Turn on Incoming Webhooks in a Slack app and add a webhook to the channel. Copy its
https://hooks.slack.com/services/...address.In the channel, add the Workflows template “Post to a channel when a webhook request is received”, and copy the address it gives. terragucci posts an Adaptive Card, the body that template reads.
-
Store the address as a secret.
A repository secret under Settings, Secrets and variables, Actions.
A masked CI/CD variable. Mark it Protected so only default-branch pipelines, where the apply jobs run, see it.
The same as GitHub; Forgejo keeps them under Settings, Actions, Secrets.
-
Name the secret in
terragucci.yml. Use either key, or both:notify:slack: SLACK_WEBHOOK_URLteams: TEAMS_WEBHOOK_URLThe value is the secret’s name.
config checkrefuses an address. -
Write the pipeline again and merge it.
Terminal window npx terragucci initThe apply jobs get the secret as
TERRAGUCCI_SLACK_WEBHOOKorTERRAGUCCI_TEAMS_WEBHOOK, and each runsterragucci notifywhen its wave stops. With a drift schedule, so does the drift job; no plan job gets it. -
Read the next message.
terragucci: wave 2 of github.com/acme/infra waits for an approvalRoots: envs/prod/app, envs/prod/dbDigest: jcs1-sha256:9f2c...Approve: terragucci approve wave-2 --plan jcs1-sha256:9f2c...Run: https://github.com/acme/infra/actions/runs/42An Adaptive Card titled with the same first line. Its facts are Wave, Roots, Digest, Approve and Run. Its button opens the run.
| If the webhook | The job |
|---|---|
| answers 2xx | logs terragucci notify: posted to Slack (or Teams) |
| answers an error, or not within 10 seconds | logs the status and goes on; the wave’s own exit code stands |
A comment that applies (/terragucci apply) posts the same messages from its job.
Drift, with a Re-plan button
Section titled “Drift, with a Re-plan button”With a drift schedule, the drift job posts to Slack and Teams when its refresh-only plans find drift, or a root cannot be refreshed. A run that finds nothing posts nothing.
terragucci: drift in github.com/acme/infra: 1 root changed outside Terraform
Drifted: envs/prod/app
Run: https://github.com/acme/infra/actions/runs/57The Re-plan button opens the page where you run the check again under your own login; it holds no token and starts nothing itself.
| Forge | Re-plan opens |
|---|---|
| GitHub | the workflow’s page, where Run workflow is |
| Forgejo | the workflow’s runs, where Run workflow is |
| GitLab | the pipeline schedules, where each schedule has a run button |
The generic webhook gets no drift event: its events are a wave’s.
Approve from the message, by review
Section titled “Approve from the message, by review”With approval: pr-review, a waiting wave’s message opens with a link to the pull request’s review page when an approving review of its head would approve the wave. The approval is still a person’s own review on the forge, and it binds the plans the pull request’s note recorded. The chat app holds no key and approves nothing.
terragucci: wave 2 of github.com/acme/infra waits for an approval
Review and approve: pull request 12, then run the wave again
Roots: envs/prod/app, envs/prod/db
Digest: jcs1-sha256:9f2c...
Or approve: terragucci approve wave-2 --plan jcs1-sha256:9f2c...
Run: https://github.com/acme/infra/actions/runs/42In Teams, the card’s first button (Review and approve) opens the same page.
| Forge | The link opens | Given when |
|---|---|---|
| GitHub | the pull request’s Files changed tab, where Review changes > Approve is | the wave’s pull request has no approving review of its head, or a reviewer asked for changes, and its plan note has a digest for the wave |
| Forgejo | the pull request’s Files changed tab, where Review > Approve is | the same |
| GitLab | the merge request, where Approve is | the same, and the merge request is still open (applied before it merges): GitLab takes no approval of a merged one |
A direct push gets no link, and neither does a refused wave or one the pull request’s note has no digest for: no review of that head can approve those plans. The message then offers the approve command alone.
After the review, run the wave again: comment /terragucci apply on the pull request, or re-run the job (Run the stage again). The wave finds the approving review of the head and records it on chant/lifecycle with via: pr-review, then applies.
Send events to your own webhook
Section titled “Send events to your own webhook”notify can also post one JSON event to an HTTPS endpoint of yours and sign it with a key you hold. A chat bot, an incident tool or a relay that approves from chat can read it; the chat setup above needs none of this.
-
Store two secrets in your forge’s store, as in step 2 above: the endpoint’s address, and a random key of 32 bytes or more, for example from
openssl rand -hex 32. Give the receiver the same key. -
Name both in
terragucci.yml. They can sit besideslackandteams.notify:webhook: TERRAGUCCI_HOOK_URLwebhook_key: TERRAGUCCI_HOOK_KEYconfig checkrefuses one without the other: an event is never sent unsigned. -
Run
npx terragucci initand merge. The apply jobs get the secrets asTERRAGUCCI_WEBHOOKandTERRAGUCCI_WEBHOOK_KEY. -
Verify each request before reading it. The body is a
terragucci.notify/v1event.Header Holds X-Terragucci-Signaturesha256=and the hex HMAC-SHA256 of the raw body with the keyX-Terragucci-Eventwaiting,refusedorfailedX-Terragucci-Deliverythe event’s id, the same for each post of one wave’s event about one digest; drop a repeatimport { createHmac, timingSafeEqual } from "node:crypto";function verified(rawBody, header, key) {const want = Buffer.from("sha256=" + createHmac("sha256", key).update(rawBody).digest("hex"));const got = Buffer.from(header ?? "");return got.length === want.length && timingSafeEqual(got, want);}
The event carries the wave’s outcome as the stage wrote it (its fields). It approves nothing, and the receiver’s answer changes nothing: a webhook that fails or is slow is logged like the chat ones.
To approve from a chat click, terragucci relay does it without this event: the buttons carry the wave and its digest, and the relay checks who clicked.
These docs count page views and clicks with PostHog. They set no cookies, store nothing in your browser, and send nothing when your browser asks not to be tracked.