ci: release automatically on merge to main - #3
Merged
Conversation
Releasing required remembering to push a tag by hand, so a merged fix could sit unreleased indefinitely. Derive the version instead: the repo already enforces Conventional Commits, so the merged subject says what the bump should be. Add .github/next-version.sh, which maps the latest tag plus a commit subject to the next version — `!`/BREAKING to major, feat to minor, fix/perf to patch, everything else to no release, so docs- and CI-only merges do not cut one. It is a script rather than inline YAML so it can be tested directly; verified against 14 cases including scoped types, version rollover (v1.9.9 -> v1.10.0), prerelease tags, and malformed input. Split release.yml into a version job that decides and tags, and a release job gated on its output. The release job cannot be chained off the tag push: a tag pushed with GITHUB_TOKEN does not trigger another workflow run, so that arrangement would silently never build. It also checks out the tag rather than the branch head, so a release stays reproducible if main moves while the job runs. Manual `git tag && git push` still works for re-cuts. A duplicate tag is a no-op rather than a failed run. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VSrrciEDBTuFNMtKocScML
SafeEval
marked this pull request as ready for review
July 30, 2026 16:59
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.
Releasing meant remembering to push a tag by hand, so a merged fix could sit unreleased indefinitely. This derives the version instead — the repo already enforces Conventional Commits, so the merged subject already says what the bump should be.
Bump rules
feat!:/BREAKING CHANGEfeat:fix:/perf:docs:/ci:/chore:/refactor:…Docs- and CI-only merges don't cut releases.
Design note: why not chain off the tag push
The obvious shape — job A pushes the tag, the existing
push: tagstrigger builds it — silently never builds. A tag pushed withGITHUB_TOKENdoes not trigger another workflow run; GitHub blocks that recursion. You'd get a tag and no release, with a green check.So
release.ymlis now two jobs: aversionjob that decides and tags, and areleasejob gated onneeds.version.outputs.tag. Both entry points converge on one build path:git push origin vX.Y.Z→ report that tag → buildThe release job also now checks out the tag rather than the branch head, so a release stays reproducible if
mainmoves while the job runs..github/next-version.shVersion math lives in a script, not inline YAML, so it's testable. Verified against 14 cases plus edge cases:
fix:→ patch,feat:→ minor,feat!:/fix(cli)!:/BREAKING CHANGEin body → majorfeat(api):),perf:→ patchdocs:/ci:/chore:/refactor:/ a rawMerge pull request …subject → no releasev1.9.9→v1.10.0,v1.0.9→v1.0.10v0.0.0, prerelease tags (v1.2.3-rc1→v1.2.4)Also simulated the version job against real repo state: current
main(fix: strip git comment lines…) computesv1.0.1, and the duplicate-tag guard was confirmed to fire by creating the tag locally and re-running — a re-run is a no-op, not a failed job.Heads-up on the first release
This PR's own commit is
feat:, so merging it cutsv1.1.0, not thev1.0.1we'd planned. Three options:v1.1.0. A minor bump for a CI change is a little odd, but harmless and honest about what happened.ci:orchore:→ no release fires; then pushv1.0.1by hand to ship the comment-stripping fix.v1.0.1first if you want the fix released under a patch number beforev1.1.0exists.I'd take (2) if you want the fix to land as
v1.0.1as planned, since GitHub squash-merges use the PR title as the subject — say the word and I'll retitle. Otherwise (1) is fine.🤖 Generated with Claude Code
https://claude.ai/code/session_01VSrrciEDBTuFNMtKocScML