How to post GitHub releases to Bluesky
Three honest paths for getting GitHub releases onto Bluesky: post by hand, a DIY GitHub Action against the free AT Protocol API, or a release-native tool - plus the link-card gotcha that makes half the DIY posts look broken.
What this solves
Someone ships from GitHub and wants each release to show up on Bluesky, and is weighing posting by hand, building a GitHub Action, or using a tool.
How S2P helps
Understand the three real paths from a GitHub release to a Bluesky post, including the free-API advantage, the 300-character limit, and the link-facet gotcha, then pick the one that fits your cadence.
Key takeaways
- Bluesky's API is free and app-password auth is simple, so it is the cheapest channel to automate yourself.
- The 300-character limit means a release post must be one sharp idea plus a link, not a changelog dump.
- Links in a raw AT Protocol post are plain text unless your automation adds facets - the classic DIY gotcha.
- A release-native tool adds drafting, clickable links, approval, and an audit trail on top of the same trigger.
Section 1
Why Bluesky earns a spot in your release loop
The developer crowd that used to make X the default build-in-public channel is now split, and a meaningful slice of it reads Bluesky. If your users are developers, your releases belong there.
Bluesky matters for release announcements for a simple reason: a large, technical, early-adopter audience moved there and kept the build-in-public habit. Open-source maintainers, indie hackers, and platform engineers who would have seen your release on X a few years ago now see it - or miss it - on Bluesky. The feed is chronological by default, which is friendly to timely posts like release announcements, and the culture rewards concrete, technical updates over marketing copy.
There is also a practical reason it is worth automating early: unlike X, the Bluesky API is free. The AT Protocol exposes a straightforward endpoint for creating posts, authentication works with an app password you generate in settings, and there is no developer-app application or paid write tier. That makes Bluesky the cheapest channel on the board to automate yourself - and it is exactly why so many GitHub Actions for it exist.
The constraint that shapes everything is length. A Bluesky post is capped at 300 characters, counted as user-perceived characters, so a release announcement has to be one sharp idea: the benefit, one proof point, and the link. That is a feature, not a limitation - it forces the discipline that makes release posts readable - but it means whatever path you choose has to be good at compressing release notes, not just forwarding them.
- A technical, build-in-public audience now reads Bluesky daily.
- The AT Protocol API is free - no developer app, no paid write tier.
- 300 characters max: benefit, proof, link - never a changelog dump.
- Chronological feeds reward timely posts, and releases are inherently timely.
Section 2
Path 1: post by hand
At a release every week or two, the manual path is honest and fine. The cost is the rewrite and remembering to do it.
The manual path needs no explanation: cut the release, open Bluesky, compress the release notes into 300 characters, paste the release link, publish. Because you write it yourself, the tone is human, the link card renders (the app handles that for you), and you decide in the moment whether this release is worth announcing at all.
It breaks down the same way it breaks on every channel: cadence. Ship several times a week and the context switch starts to bite - you have to remember, re-read the release, compress it, and post, every time, on every channel you care about. Bluesky is rarely anyone's only channel, so the manual cost multiplies by however many destinations your releases deserve. The post that never gets written is the failure mode, and it is invisible.
Treat manual posting as the correct default until you notice yourself skipping announcements you meant to write. When that happens, Bluesky's free API means you have a genuinely cheap DIY option - with two gotchas the next section is honest about.
- Right default at low volume: free, human, fully controlled.
- The app renders link cards for you - no facet work when posting by hand.
- Breaks down on cadence, and multiplies across channels.
- The signal to automate: skipped announcements, not difficulty.
Section 3
Path 2: the DIY GitHub Action
This is the easiest social channel to automate yourself: a free API, app-password auth, and several maintained community actions. Here is a realistic sketch.
A DIY automation triggers on the GitHub release event and calls the AT Protocol's create-record endpoint to publish a post. In practice nobody hand-rolls the API call: community actions on the GitHub Marketplace - zentered/bluesky-post-action and cbrgm/bluesky-github-action are the established ones - wrap authentication and posting. Setup is two secrets: your handle and an app password generated in Bluesky's settings, so your real password never touches CI. No developer application, no billing.
The workflow below is a realistic version using cbrgm/bluesky-github-action, which matters for one specific reason: it can convert the URLs in your post into rich link embeds. On the AT Protocol, a URL in the post text is just text - it is not clickable and shows no preview card unless the client that created the post attached facets (the metadata that marks a range of text as a link) or an external embed. This is the classic DIY gotcha: the naive script posts fine, and every link in it is dead text.
Be equally honest about what the workflow does not do. It posts the same fixed template for every release, so a major launch and a typo-fix patch look identical. It publishes the instant the release fires, with no human reading it first. There is no retry when the API hiccups, no record tying the post back to the release, and a 300-character budget that a raw tag-plus-URL template spends badly. For a personal project, none of that matters. For a brand account, all of it does.
.github/workflows/bluesky-on-release.yml
name: Post release to Bluesky
on:
release:
types: [published]
jobs:
post:
runs-on: ubuntu-latest
steps:
- name: Send Bluesky post
# Auth is an app password from Bluesky settings - never your real
# password. The API is free; there is no developer app to create.
uses: cbrgm/bluesky-github-action@v1
with:
handle: ${{ secrets.BLUESKY_HANDLE }}
password: ${{ secrets.BLUESKY_APP_PASSWORD }}
text: |
Shipped ${{ github.event.release.tag_name }}
${{ github.event.release.html_url }}Section 4
The gotcha: links are not links unless you make them links
The AT Protocol does not auto-detect URLs. If your automation skips facets, your release link publishes as plain, unclickable text.
This deserves its own section because it is the thing that quietly ruins DIY Bluesky automations. In Bluesky's data model, a post is a text record, and anything interactive - links, mentions, hashtags - is described by facets: byte-range annotations attached alongside the text. The official apps compute facets for you when you type, which is why manual posts always work. A script that just sets the text field gets exactly what it asked for: the URL rendered as inert characters.
A release announcement without a clickable release link is close to worthless, so whatever you build has to detect the URL, compute the byte range, and attach the facet - or attach an external embed for the preview card. The better community actions handle this for you, which is why choosing one that supports link embeds matters more than any other feature. If you write your own client code, budget real time for facet handling, because byte ranges over UTF-8 text are exactly the kind of detail that works in the demo and breaks on the first release note with an emoji.
This is also a fair preview of what tool-versus-DIY means on Bluesky. The API being free removes the billing argument that dominates the X decision, so the remaining difference is entirely in the details: correct facets, compression into 300 characters that reads like a human wrote it, approval before it goes public, and a record of what was posted for which release.
- AT Protocol posts carry facets - byte-range annotations - for anything clickable.
- Scripts that only set post text publish URLs as dead, unclickable text.
- Pick a community action with link-embed support, or budget facet work.
- With billing off the table, correctness and workflow are the whole DIY-vs-tool gap.
Section 5
Path 3: the release-native tool path
The point of a tool on Bluesky is not API access - that is free. It is drafting that respects 300 characters, links that work, and a human check before your brand account posts.
S2P connects your GitHub repository and your Bluesky account through a managed connection, so there is no app password sitting in CI secrets. When you cut a release, it drafts a Bluesky-native post from the release notes: one sharp idea compressed into the 300-character budget in your brand voice, with the release link attached correctly so it is clickable and carries a preview. Rules decide which releases qualify - semver, branch, path, label - so a dependency-bump patch never spams your followers.
The draft then goes into a review queue. A human reads it, edits if needed, and approves - or, once you trust the loop, you flip that channel to autonomous mode and let qualifying releases publish on their own. Either way every published post stays tied to the release that created it, retries are handled when the API blips, and the same release fans out to the rest of your channels - X, LinkedIn, Discord, Reddit, Mastodon - without another rewrite.
The honest comparison: if you ship rarely, post by hand. If you enjoy owning the workflow and one channel is enough, the DIY Action is genuinely cheap here - Bluesky is the best DIY channel on the board. If your releases should reach Bluesky and several other channels with drafting, approval, and an audit trail included, the release-native path is the one that stays cheap in time rather than money. Want to feel the drafting before connecting anything? The free changelog-to-social generator has a Bluesky mode: paste release notes, get a 300-character post, no login.
- Managed Bluesky connection - no app password in your CI secrets.
- Drafts compress the release into 300 characters, with working links.
- Approval by default, autonomous mode per channel when you trust it.
- One release fans out to Bluesky plus every other channel you run.
Section 6
Which path fits your team
Bluesky flips the usual math: DIY is actually free here. The decision is about correctness, review, and how many channels you feed - not the API bill.
Map it to cadence, like every channel. Announce a release every week or two: post by hand and spend nothing. Ship often with an engineer happy to own a workflow: the DIY Action is legitimately the strongest DIY case of any social channel, because auth is an app password and the API costs nothing - just pick an action with link-embed support and accept that every release gets the same template and no review. Ship often to multiple channels with a brand to protect: use a release-native tool and keep a human in the loop.
If you are weighing the DIY route more broadly - Actions versus webhooks versus scripts across all your channels - the GitHub Actions versus webhooks comparison lays out the full decision matrix; the short version is that the workflow around the post, not the API call, is where the real cost lives. Bluesky just happens to be the channel where the API call itself is free.
Whatever you pick, the goal is unchanged: make announcing a release so cheap you never skip one. Bluesky's audience rewards teams that ship visibly and speak plainly. A steady stream of honest, 300-character release posts is exactly the kind of presence that compounds there.
- Ship rarely: post by hand - free and human.
- One channel, engineer-owned: DIY Action - the best DIY channel there is.
- Multi-channel with a brand to protect: release-native tool with approval.
- Whatever the path: benefit, proof, link - inside 300 characters.
FAQ
Questions this article answers
Is the Bluesky API free for posting?
Yes. The AT Protocol API that Bluesky runs on is free to use for posting. You authenticate with an app password generated in your Bluesky settings - there is no developer application, no approval process, and no paid write tier. That makes Bluesky the cheapest social channel to automate release posts for.
Is there a GitHub Action that posts to Bluesky?
Yes, several community actions on the GitHub Marketplace do this, including zentered/bluesky-post-action and cbrgm/bluesky-github-action. Trigger one on the release event and it posts on your behalf using a handle and app password stored as repository secrets. Prefer an action that supports link embeds, or your release URL will publish as unclickable text.
Why is the link in my automated Bluesky post not clickable?
Because the AT Protocol does not auto-detect URLs. Links, mentions, and hashtags are only interactive if the posting client attaches facets - byte-range annotations - or an external embed alongside the post text. Official apps do this automatically; a script that only sets the text field publishes the URL as plain text. Use a posting library or action that computes facets.
How long can a Bluesky post be?
300 characters, counted as user-perceived characters (graphemes), so emoji and accented characters count once. For a release announcement that means one benefit-first sentence, one concrete proof point, and the release link. Automation that just forwards release notes will get truncated or rejected, so drafting that compresses well matters more on Bluesky than on most channels.
Can S2P post GitHub releases to Bluesky automatically?
Yes. S2P connects to Bluesky through a managed connection, drafts a channel-native post from each qualifying GitHub release within the 300-character limit, attaches the release link correctly so it is clickable, and routes the draft for approval before publishing. You can also enable autonomous mode per channel once you trust the loop, and every post stays tied to its source release.
Should I announce every release on Bluesky?
No. Announce releases that change something a user can feel: features, meaningful fixes, performance work. Dependency bumps and internal chores dilute the signal that makes a build-in-public feed worth following. Trigger rules on semver, branch, or label are the systematic way to make that call once instead of per release.
Related guides and pages
Where to go next
Hand-picked pages that go deeper on the workflow, channels, and tooling covered above.
