chore(release): automate the ritual around goreleaser - #4
Merged
Conversation
Publishing was already automated; the ritual around it was not, and that is where both previous releases went wrong. v0.1.1 published a GitHub release and then failed to push the Homebrew formula, leaving a green-looking tag and users on the old version — the formula had to be published by hand. v0.2.0 was nearly cut from a main whose CI was red. Three additions, each aimed at one of those: - scripts/release.sh (make release VERSION=vX.Y.Z) refuses before it tags (on main, clean, in sync, tag unused, CI green FOR THIS COMMIT — matched on SHA so a green run for an older commit cannot vouch for this one), then watches the workflow and verifies what a user actually gets: release assets exist, the formula reports the new version, and one real asset's SHA-256 matches the formula. A green workflow is not proof of any of that. - release.yml verifies the tap token can write BEFORE anything publishes. goreleaser pushes the formula last, so a bad token yields the worst available outcome: artifacts out, formula stale, run red. Two seconds of curl turns that into a failure that publishes nothing. - tap-token-canary.yml checks the token weekly. Fine-grained PATs expire after 30 days by default and nothing observes it until a release tries to publish; this makes the next expiry a failed cron instead. The tap is a public repo, so a token with no grant still gets 200 and the full repo JSON back — only `permissions` is missing. Both checks therefore assert `.permissions.push == true` rather than trusting a status code. docs/RELEASING.md records the procedure and why each check exists. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Publishing was already automated. The ritual around it was not, and that is where both previous releases went wrong:
403, under-scoped token). Green-looking tag, stale formula, users silently on the old version. The formula was published by hand.mainwhose CI was red — six login tests passed on macOS and failed on the Linux runner. Nothing would have stopped the tag.What this adds
scripts/release.sh—make release VERSION=v0.2.1Refuses before tagging: on
main, clean tree, in sync with origin, tag unused, and CI green for this exact commit (matched on SHA, so a green run for an older commit cannot vouch for this one).Verifies after publishing, because a green workflow is not proof the release is usable: assets exist, the formula reports the new version, and one really-downloaded asset's SHA-256 matches the formula. A checksum mismatch otherwise surfaces only in somebody's
brew install.release.yml— verifies the tap token can write before anything publishes. goreleaser pushes the formula last, so a bad token gives the worst outcome available. Two seconds ofcurlinstead.tap-token-canary.yml— weekly check. Fine-grained PATs expire after 30 days by default and nothing observes it until a release tries to publish. Now the next expiry is a failed cron on a Monday.One detail worth knowing
homebrew-tapis public, so a token with no grant still receives200and the full repository JSON — only thepermissionsblock is missing. Both checks therefore assert.permissions.push == truerather than trusting a status code. (And public visibility never removed the need for a token: reads are anonymous, writes are not.)Verification
Refusal paths exercised directly: bad version format, wrong branch, dirty tree. The CI-green jq query was validated both ways —
completed successfororigin/main,missing -for an unknown SHA. All three workflow files parse. No release behaviour changes on the happy path.🤖 Generated with Claude Code