post release contents to Discord, not just the link - #581
Merged
jrhizor merged 5 commits intoAug 20, 2026
Conversation
The release workflow announced a new version as a bare link, so the
#releases channel showed a URL and nothing about what actually shipped —
and the URL unfurled into a media embed that repeated the same link.
The workflow already generates the release body into `release-notes.md`
one step earlier for `gh release create`. Reuse that file: render it as
the Discord message body with the release link at the bottom.
Adds `scripts/discord-release-payload.mjs`, which builds the webhook
payload, following the existing `scripts/*.mjs` pattern rather than
growing the inline `jq` program:
- trims the notes to Discord's 2000-character `content` limit, budgeting
for the header, the trailing link, and the separating newlines, and
cutting on a whole code point so an emoji in the notes cannot be split
into an invalid surrogate half
- sets `flags: 4` (SUPPRESS_EMBEDS) so the link no longer unfurls
- sets `allowed_mentions: { parse: [] }` so a commit message containing
@everyone cannot ping the server
- degrades to the previous link-only announcement when the notes file is
missing or empty, so a Discord problem still cannot fail a release
Closes elmohq#449
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
@dchaudhari7177 is attempting to deploy a commit to the Blue Whale Labs Team on Vercel. A member of the Team first needs to authorize it. |
The compare link lived at the end of the notes, so the one case that most wanted it — a release too long for Discord's 2000-character limit — was exactly the case that cut it off. Parse it out of the notes and render it with the release link as a footer reserved ahead of the body. Overflow now drops whole bullets and says how many, instead of stopping mid-word; only a single bullet larger than the entire budget still falls back to a character cut. Bare pull request URLs are masked to elmohq#123. GitHub shortens those itself, Discord does not, and webhook messages honour masked links. Also drops the "What's Changed" heading, which restated the header above it, and folds the two trailing links into one footer line. Guard the payload build the way the curl below it is already guarded: the packages are published by the time this step runs, so a failure here must not fail the job and strand the Docker builds.
Release notes are assembled from changeset files and commit subjects, so their text is contributor-authored and reaches a channel post unreviewed once merged. Two places handed that text more trust than it had earned. The footer took whatever URL followed "Full Changelog:" and rendered it behind that label, so a crafted line could put an arbitrary destination behind trustworthy-looking text in an official announcement. It now has to point into this repository; anything else stays in the body with its URL on show. Pull request masking was matching any repository, so a link elsewhere rendered as a bare "elmohq#1" — which in an Elmo announcement reads as an Elmo pull request. Masking is now scoped to this repository too.
The fallback hardcoded the project's identity into a script whose entire job is generating links to it, and being a fallback it had no way to say so — a misconfigured run would quietly announce a release with links pointing at somebody else's repository. The workflow has always passed $REPO, so the default only ever covered ad-hoc local runs. Also replaces the character-walk in truncate() with a slice and a single surrogate check, and trims the comments down to the parts the code cannot say for itself.
Covers the contract the release workflow depends on: the announcement says what shipped, stays inside Discord's 2000-character limit while keeping both links, survives unreadable notes, and refuses to let contributor-written notes ping the channel or put a foreign URL behind the changelog label. Driven through the CLI rather than the internals, so the parsing and fitting helpers stay free to change. Uses node:test to keep scripts/ dependency-free; turbo does not reach outside the workspace, so CI runs it as its own step.
jrhizor
approved these changes
Aug 20, 2026
jrhizor
left a comment
Contributor
There was a problem hiding this comment.
Thanks for the contribution! I made a couple small changes and will merge now.
Looking forward to using it for the release this week!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #449.
Problem
The
Announce release on Discordstep posted only a link:payload=$(jq -n --arg content "🚀 **Elmo v${VERSION}** is out! ${release_url}" '{content: $content}')so #releases showed a URL and nothing about what shipped — and the URL unfurled into a media embed repeating the same link.
Approach
The workflow already generates the release body one step earlier for
gh release create:So the notes are already sitting in the workspace. This PR reuses that file rather than regenerating or reformatting anything, which keeps the Discord post and the GitHub release permanently in sync.
The payload construction moved out of inline
jqintoscripts/discord-release-payload.mjs, matching the existingscripts/*.mjspattern — the truncation logic below is more than a shell one-liner should carry, and as a script it is runnable locally.What the script handles
contentlimit. Notes are trimmed to what is left after the header, the trailing link and the separating newlines. A long release would otherwise make Discord reject the whole message and the announcement would silently fail.flags: 4(SUPPRESS_EMBEDS) so the link no longer unfurls — the "suppress the media embed" part of the issue.allowed_mentions: { parse: [] }so a commit message containing@everyonecannot ping the server. Not requested, but the notes now carry arbitrary commit text into a channel post, so it seemed worth closing.release-notes.mdis missing or empty. A notes problem must not fail an otherwise-successful release, matching the existing non-fatalcurl.Validation
jqis not needed anymore, and the script was exercised directly against fixtures:.github/workflows/publish.yamlre-parsed withyaml.safe_loadafter the edit to confirm the step is still well-formed. Biome ignoresscripts/, consistent with the other scripts there.One note on rendering
Discord renders
##headings and*bullets, so the generated notes display close to the GitHub release. I could not verify against a live webhook — I have no Discord webhook for this project — so the exact rendering is worth one real release to confirm.CLA
I have not added a username to
.github/contributors.txt. Signing the CLA is a legal act that has to come from the account owner, so @dchaudhari7177 will add that line before this can merge.🤖 Generated with Claude Code