Skip to content

post release contents to Discord, not just the link - #581

Merged
jrhizor merged 5 commits into
elmohq:mainfrom
dchaudhari7177:feat/449-discord-release-contents
Aug 20, 2026
Merged

post release contents to Discord, not just the link#581
jrhizor merged 5 commits into
elmohq:mainfrom
dchaudhari7177:feat/449-discord-release-contents

Conversation

@dchaudhari7177

Copy link
Copy Markdown
Contributor

Closes #449.

Problem

The Announce release on Discord step 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:

node scripts/generate-release-notes.mjs "$VERSION" > release-notes.md

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 jq into scripts/discord-release-payload.mjs, matching the existing scripts/*.mjs pattern — 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

  • 2000-character content limit. 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.
  • Cuts on a whole code point. Release notes carry emoji; a naive slice can split one into a lone surrogate, which is invalid UTF-8 in the JSON payload.
  • 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 @everyone cannot ping the server. Not requested, but the notes now carry arbitrary commit text into a channel post, so it seemed worth closing.
  • Degrades to the old link-only announcement when release-notes.md is missing or empty. A notes problem must not fail an otherwise-successful release, matching the existing non-fatal curl.

Validation

jq is not needed anymore, and the script was exercised directly against fixtures:

case result
real notes (2 PRs + Full Changelog) renders heading, bullets, link last
3001-char notes content exactly 2000, ends with link, ellipsis present
cut landing mid-emoji emoji dropped whole, no lone surrogate, UTF-8 round-trips
missing notes file falls back to link-only, warns on stderr
no notes argument falls back to link-only

.github/workflows/publish.yaml re-parsed with yaml.safe_load after the edit to confirm the step is still well-formed. Biome ignores scripts/, 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

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>
@vercel

vercel Bot commented Aug 13, 2026

Copy link
Copy Markdown

@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 jrhizor changed the title feat(release): post release contents to Discord, not just the link Aug 20, 2026

@jrhizor jrhizor left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the contribution! I made a couple small changes and will merge now.

Looking forward to using it for the release this week!

@jrhizor
jrhizor merged commit c1e395a into elmohq:main Aug 20, 2026
6 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants