Skip to content

ci: release automatically on merge to main - #3

Merged
SafeEval merged 1 commit into
mainfrom
agent/auto-release
Jul 30, 2026
Merged

ci: release automatically on merge to main#3
SafeEval merged 1 commit into
mainfrom
agent/auto-release

Conversation

@SafeEval

Copy link
Copy Markdown
Member

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

Merged commit Bump
feat!: / BREAKING CHANGE major
feat: minor
fix: / perf: patch
docs: / ci: / chore: / refactor: no release

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: tags trigger builds it — silently never builds. A tag pushed with GITHUB_TOKEN does not trigger another workflow run; GitHub blocks that recursion. You'd get a tag and no release, with a green check.

So release.yml is now two jobs: a version job that decides and tags, and a release job gated on needs.version.outputs.tag. Both entry points converge on one build path:

  • merge to main → derive version → tag → build
  • manual git push origin vX.Y.Z → report that tag → build

The release job also now checks out the tag rather than the branch head, so a release stays reproducible if main moves while the job runs.

.github/next-version.sh

Version 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 CHANGE in body → major
  • scoped types (feat(api):), perf: → patch
  • docs: / ci: / chore: / refactor: / a raw Merge pull request … subject → no release
  • rollover: v1.9.9v1.10.0, v1.0.9v1.0.10
  • first release from v0.0.0, prerelease tags (v1.2.3-rc1v1.2.4)
  • malformed tag → exits 1 rather than silently mis-tagging

Also simulated the version job against real repo state: current main (fix: strip git comment lines…) computes v1.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 cuts v1.1.0, not the v1.0.1 we'd planned. Three options:

  1. Merge as-is → first automated release is v1.1.0. A minor bump for a CI change is a little odd, but harmless and honest about what happened.
  2. Squash-merge with the subject retitled ci: or chore: → no release fires; then push v1.0.1 by hand to ship the comment-stripping fix.
  3. Merge as-is, then hand-push v1.0.1 first if you want the fix released under a patch number before v1.1.0 exists.

I'd take (2) if you want the fix to land as v1.0.1 as 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

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
SafeEval marked this pull request as ready for review July 30, 2026 16:59
@SafeEval SafeEval changed the title feat: release automatically on merge to main ci: release automatically on merge to main Jul 30, 2026
@SafeEval
SafeEval merged commit 1c3a0df into main Jul 30, 2026
1 check passed
@SafeEval
SafeEval deleted the agent/auto-release branch July 30, 2026 17:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant