discord-release¶
Posts a release announcement to a Discord channel when a vX.Y.Z tag
pipeline runs. Parses the tag, decides whether the release is worth
announcing, pulls the notes from the GitLab Releases API and posts a
Discord embed.
Pairs with releaser-pleaser and
release-plz: those cut the tag and the
release, this one tells people about it.
The filter is the point
An announcements channel fed by every patch bump across the estate
stops being read within a week, and a channel nobody reads is worse
than no channel. The default is minor and major cuts only — any
tag whose patch component is 0. Headline projects opt into patch
announcements with announce_patches: true; everything else stays
quiet until its next feature release.
Jobs¶
| Job | What it runs |
|---|---|
discord-release (rename with job_name) |
Parses $CI_COMMIT_TAG, applies the filter, fetches release notes, and POSTs a Discord embed. Writes the payload to discord-release/<job_name>.json as an artifact whether or not it posts, so an announcement can be inspected after the fact. interruptible: false — a tag pipeline should not be cancelled by a later push. |
Inputs¶
| Input | Type | Default | Description |
|---|---|---|---|
stage |
string | notify |
GitLab CI stage. Consumers must declare it in stages:. |
image_version |
string | "v0.1.1" |
ci-base tag to run in. Needs curl and jq — 48 MB, against the 611 MB this job used to pull for them. |
job_name |
string | discord-release |
Name of the generated job. Override to include the component more than once, e.g. a second channel. |
webhook_url |
string | "$DISCORD_RELEASE_WEBHOOK" |
Discord webhook URL. Defaults to the group-level variable so a consumer needs no per-project config. Empty means "skip quietly". |
announce_patches |
boolean | false |
Announce patch releases (vX.Y.Z where Z is not 0). Reserved for headline projects. |
announce_prereleases |
boolean | false |
Announce prerelease tags (v1.3.0-rc.1 and friends). |
tag |
string | "$CI_COMMIT_TAG" |
The tag to announce. Override to re-announce an earlier release, or to exercise the filter. |
if |
string | "$CI_COMMIT_TAG" |
Gating rules:if expression — by default, "this pipeline has a tag". The schedule-never guard is always applied ahead of it. Mirrors the same input on releaser-pleaser. |
project_name |
string | "$CI_PROJECT_TITLE" |
Human name used in the embed title. |
notes_limit |
number | 1200 |
Characters of release notes to include. Discord's ceiling is 4096; the lower default keeps an announcement scannable. |
notes_retries |
number | 5 |
Polls of the Releases API before announcing without notes. |
token |
string | "$CI_JOB_TOKEN" |
Token used to read the Releases API. Public projects need none. |
dry_run |
boolean | false |
Build and print the payload without posting. |
allow_failure |
boolean | true |
Whether a failed announcement fails the pipeline. Leave true in consumers. |
What gets announced¶
| Tag | announce_patches |
Result | Embed colour |
|---|---|---|---|
v1.0.0 |
either | announced, major | amber #F8C272 |
v1.2.0 |
either | announced, minor | teal #1F6E70 |
v0.4.0 |
either | announced, minor | teal #1F6E70 |
v1.2.3 |
false |
quiet | — |
v1.2.3 |
true |
announced, patch | deep #0C292E |
v1.3.0-rc.1 |
either | quiet unless announce_prereleases |
teal |
nightly-2026-08-09 |
either | quiet (not semver) | — |
"Major" here means MINOR and PATCH are both 0, so on a pre-1.0
project v0.4.0 reads as a minor cut, which is what it is.
Minimal usage¶
stages:
- notify
include:
- component: gitlab.com/phpboyscout/cicd/[email protected]
A headline project that wants its patches announced too:
include:
- component: gitlab.com/phpboyscout/cicd/[email protected]
inputs:
announce_patches: true
Failure behaviour¶
Nothing here is allowed to break a release.
allow_failure: trueby default. A Discord outage, a rotated webhook or a rate-limit must not fail the pipeline that just cut a release.- A missing webhook is a skip, not a failure. The job warns and
exits
0. This is deliberate: the component can be rolled out across the estate before the webhook variable exists, and starts working the moment it does. - Release notes are best-effort. The tag pipeline can start before
releaser-pleaserhas created the Release object, so the lookup retries (notes_retries) and then degrades to an embed with no body rather than failing or announcing nothing. - An unrecognised tag is a skip. Anything that is not
vMAJOR.MINOR.PATCH[-PRERELEASE]exits0without posting.
Setting up the webhook¶
The webhook is created in Discord, not here: Server Settings →
Integrations → Webhooks → New Webhook, pointed at the announcements
channel. Copy the URL and store it as a masked group-level CI
variable named DISCORD_RELEASE_WEBHOOK.
The webhook URL is a credential
Anyone holding it can post to the channel as that webhook. Keep the variable masked. Rotate it in Discord if it ever reaches a log or a public MR.
v* must be a protected tag in the consuming project
DISCORD_RELEASE_WEBHOOK is a protected group variable, so
GitLab only exposes it to jobs running on protected refs. Every
phpboyscout project protects v* at Maintainer level, which is what
makes a release tag a protected ref and lets the variable through.
A project that does not protect its v* tags gets an empty webhook,
and the component treats an empty webhook as "skip quietly" — so the
job goes green and nothing is ever announced. That is silent by
design (it is what allows rollout before the webhook exists), which
makes this the one setting worth checking when a new project's
releases never show up in the channel.
Set it with: