roots/discourse-topic-github-release-action: The GitHub Action That Makes Release Notes Feel Human
It does more than post changelogs to Discourse. It pulls org-wide username mappings from `.github`, assigns the right release author, and turns automation into a community-facing voice.
- This action is less about posting releases than about preserving authorship as announcements move from GitHub into community space.
- Its clearest invention is an org-level `discourse.yml` phonebook in `.github`, which makes identity mapping reusable across repositories.
- The implementation stays small by using a simple Discourse API call, defensive fallbacks, and an output URL that downstream steps can reuse.
- Compared with scripts or server-side plugins, it puts the trust boundary in the GitHub organization, where maintainers already manage release workflow.
Release Automation With a Face
Most release automation is invisible plumbing. This repo makes the plumbing matter because it decides whether a Discourse topic looks like it came from a bot or from the person who actually cut the release. That subtle shift changes the tone of the announcement from machine output to community message.
Post published GitHub releases from a repository to a Discourse instance.
That one sentence is the surface description. The real trick is farther down, where the action uses release metadata, looks up a username mapping, and posts under a human identity when it can. In open source communities, that is not a cosmetic detail. It is part of the product.
The Org-Level Phonebook Hidden in `.github`
This is the project’s sharpest architectural idea. Most GitHub Actions keep configuration local to the repository. This one reaches into the organization’s shared `.github` repo and treats identity as a central resource instead of a per-repo one.
| Approach | Setup effort | Identity handling | Where config lives | Needs Discourse admin access? | Reusable across repos |
|---|---|---|---|---|---|
| Generic script with `curl` or `actions/github-script` | Low at first, higher over time | Usually manual or ad hoc | Each repo | Usually no | Limited |
| `roots/discourse-topic-github-release-action` | Low | Shared mapping plus fallback | Organization `.github` | No | High |
| Discourse plugin or server-side integration | High | Deep platform-level control | Discourse server | Yes | High, but heavier |
The comparison is not really about features. It is about trust boundaries. Scripts put the burden in each repository. Plugins move it onto the Discourse server. This action puts the important part, the identity map, into the GitHub organization where release maintainers already work.
How the Action Decides Who to Be
const release = github.context.payload.release;
const repo = github.context.repo.repo;
const releaseAuthor = release.author.login;
let discourseUsername = core.getInput('discourse-author-username');
if (!discourseUsername) {
const usernameMapping = await fetchUsernameMapping();
discourseUsername = usernameMapping[releaseAuthor] || 'system';
}
const body = `\n${release.body}\n\nSee the release on GitHub: ${release.html_url}`;
await client.postJson('/posts.json', {
title: `${repo} ${release.tag_name} released`,
raw: body,
category: parseInt(core.getInput('discourse-category'), 10),
tags: core.getMultilineInput('discourse-tags')
}, {
headers: { 'Api-Username': discourseUsername }
});
The sequence is straightforward, which is part of the appeal. First comes a manual override. If that is absent, the action tries the shared mapping file. If that fails too, it falls back to `system` so the release pipeline does not break over missing identity data.
The action automatically maps GitHub usernames to Discourse usernames by reading a discourse.yml file from your organization's .github repository.
That fallback order is the story. The action is not trying to be clever for its own sake. It is trying to keep release communication flowing while still preserving a person-shaped author field whenever the mapping exists.
Why This Is Better Than a Shell Script
A shell script can absolutely post to Discourse. So can `actions/github-script`. But those approaches make every repository re-encode the same choices: how to build the body, which category to use, which tags to apply, and how to decide who the post should come from. This action packages those decisions once.
It also gives downstream steps a usable `topic-url` output, which matters if release publishing is part of a larger workflow. The post does not just happen. It becomes a linkable artifact that other automation can reuse.
| Trait | Shell script | Generic GitHub Action | This action |
|---|---|---|---|
| Release body handling | Hand-built | Hand-built | Built in |
| Identity mapping | Custom work | Custom work | Shared org phonebook |
| Output URL | Usually manual | Usually manual | Provided as `topic-url` |
| Operational scope | Per repo | Per repo | Per organization |
| Community tone | Easy to flatten into bot output | Easy to flatten into bot output | Designed to preserve authorship |
A Discourse plugin could go deeper, but that depth comes with server access, maintenance, and ownership on the forum side. This repo makes a different trade. It is lighter, easier to adopt, and good enough for the workflow most open source orgs actually need.
What the Maintainers Optimized For
The implementation choices point to discipline. It uses `@actions/http-client` rather than a heavier API wrapper, bundles with `@vercel/ncc`, and targets Node 24 in the action manifest. That keeps the runtime small and predictable.
The code also favors defensive defaults. Missing mappings do not explode the workflow. The action logs clearly, falls back cleanly, and keeps the release moving. That is what a production utility should do when its job is to support communication, not become the headline.
runs:
using: node24
main: dist/index.js
outputs:
topic-url:
description: URL of the created Discourse topic
The minimalist shape is not a lack of ambition. It is the point. This is a small tool with a precise job, and it avoids turning that job into an ecosystem.
The Real Product Is Community Continuity
Seen one way, this repo posts release notes to a forum. Seen the right way, it keeps the release voice continuous across GitHub and Discourse without reducing people to a generic bot account. That is a small technical choice with a big social effect.
For community-driven orgs, that matters. Releases are not just changelogs. They are public moments, and this action helps those moments keep a human face.





