All posts
Engineering15 Sep 2026 · 2 min read

The changelog that shipped 105 PR titles

JPJean Perez

The first live run of the changelog pipeline published 105 PR titles, verbatim, as the v1.1.0 release notes.

That wasn't the plan. changelog-release.yml is ported from a sister project. On every push to main it collects the PRs merged since the last tag, sorts them into categories and picks a version bump: minor if eight or more PRs landed, patch otherwise. From there it rewrites the categorized list into customer-facing prose with Claude Haiku, writes an MDX changelog entry, and cuts a GitHub release. It has a fallback for when the rewrite step can't run: a deterministic formatter that just lists the merged PR titles in place of prose. A local dry run before merge found 120 merged PRs since the last tag and proposed v1.1.0. The logic was fine.

What actually happened on merge is that no ANTHROPIC_API_KEY secret existed yet. The rewrite step had nothing to authenticate with, so the fallback ran instead. What went live on /changelog was 105 raw PR titles, one per line, published as the public-facing description of what v1.1.0 shipped.

The fallback did its job

Nothing actually broke. The fallback exists so that a missing key doesn't fail silently, and it did that: it published something true, just not something readable. A follow-up PR rewrote that entry into prose by hand and added a skip-changelog label to the repo so a curation PR like itself doesn't trigger the pipeline into cutting another release on top of the fix.

That's the symptom fixed. The cause got fixed the same day, too.

Replacing the key with a workflow identity

The next PR replaced the ANTHROPIC_API_KEY secret entirely with GitHub Actions' OIDC identity federation. The workflow requests id-token: write, exchanges its GitHub-issued JWT for a short-lived Anthropic bearer token at /v1/oauth/token, and never stores or rotates a long-lived key. A rule in the Anthropic Console, scoped to this repository and to pushes on main, decides whether the exchange succeeds. There is no secret to go missing. If the exchange is ever denied, the workflow falls back to the same deterministic parser that shipped v1.1.0 in the first place, non-fatal by design.

A gate in front of the paid build

One more thing shipped alongside the pipeline: a DESKTOP_AUTO_RELEASE repo variable, unset by default. Without it, a version bump on main never triggers the desktop release workflow automatically, even though the two pipelines share a versioning scheme and the same tag would otherwise be a natural trigger for both. A desktop build with macOS in it costs about $5 a run, and a merge shouldn't be able to spend that on its own. Dispatching a desktop release stays a manual action until the variable is set.