Repository navigation
fix(ci): block major bumps before ccv pushes the tag - #25
Merged
Merged
Conversation
smlx/ccv defaults to write-tag: true, so the action created and pushed the tag before the "skip release on major" condition ran. A breaking commit would leave an orphan, unreleased major tag that cannot be deleted (tag ruleset). - Run ccv with write-tag: false and push the tag in a separate step, only after validating the version and blocking major bumps. - Fail the run on a major bump, with a job summary that pins the commit SHA for the manual release. - Push only from main, and set pushed=true only when the tag was newly created (git push --porcelain), so the release runs exactly once. - Add a dry_run dispatch input, job-level concurrency, a timeout, and explicit persist-credentials/permission comments. - Document automated and manual major releases in CONTRIBUTING.md.
Owner
Author
|
Dry run from this branch against the real repo (run 39839164672): computed |
4 of 5 tasks
clappingmonkey
added a commit
that referenced
this pull request
Oct 22, 2026
…majors (#26) ## What does this PR do? Follow-up to #25. A dry run on `main` after #25 merged computed `v1.0.0`. The squash commit body (the PR description) quoted ccv's breaking-change footer text, and ccv v0.10.0 matches that text anywhere in a commit message. The guard from #25 worked and nothing was tagged. Still, the failure gave no hint about which commit caused the bump or how to recover. This PR changes: - `tag.yaml`: when the computed bump is major, a new **List breaking commits** step (best effort, `continue-on-error`) writes the likely trigger commits since the last tag to the job summary, with the matching body lines. Commit text is sanitized so it cannot break out of the code fence. - `tag.yaml`: the blocked-run summary prints two runbooks, both pinned to the run SHA: - **Intended major**: tag the computed `vX.0.0`. - **Accidental bump**: tag the next patch, which the workflow computes. ccv stops at the first tagged commit it reaches, so later runs only count commits after it. Both use `git -c tag.gpgSign=false tag`, because ccv ignores annotated and signed tags. - `CONTRIBUTING.md`: the "Major releases" section explains which forms ccv treats as breaking, warns that the footer text is matched anywhere (including the PR description), and documents both recovery paths. - `PULL_REQUEST_TEMPLATE.md`: tells authors to mark breaking changes with `!` in the PR title, and not to write the footer text in the description of a non-breaking PR. The `ci:` title does not bump the version. ## How was it tested? - `actionlint` is clean. `zizmor --persona=pedantic` only reports the existing `self-repository` low finding. `pre-commit run --all-files` passes. - Sandbox repo (private, same workflow, stubbed release): - A dry run on a pending `feat!:` succeeds and lists the commit. - A normal run is blocked and no tag is created. - A `fix:` commit whose body contains the footer text is blocked, and the list shows it. - Running the printed accidental-bump commands creates a lightweight `v3.1.2`. The push triggers the release exactly once. - The next `fix:` is auto-tagged `v3.1.3`, released once, and the list step is skipped. - I rendered the summary against this repo's `main`. It lists `d52682f` with the matching body line and suggests `v0.4.2` as the recovery tag. - Dry run of this branch on this repo: see the comment below. - Reviewed by code-reviewer and change-validator. All findings were addressed, and change-validator's final verdict is APPROVE. ## Checklist - [x] PR title follows [conventional commit](https://www.conventionalcommits.org/) format - [ ] CI passes (`check.yaml` — lint + test matrix) - [x] Tests added or updated for new/changed behavior (sandbox runs above; workflow-only change) - [x] Documentation updated if user-facing (CONTRIBUTING.md, PR template) - [x] No breaking changes
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.
What does this PR do?
smlx/ccvcreates and pushes the version tag inside its own step (write-tagdefaults totrue). The "block major bump" condition intag.yamlonly gated thereleasejob, so afeat!:/fix!:/BREAKING CHANGE:commit would push an orphanvX.0.0tag that never gets released. Thev*tag ruleset blocks deletion, so that tag could never be removed.This change makes ccv compute the version only and moves tagging into an explicit step that runs after the guard.
.github/workflows/tag.yamlwrite-tag: false.refs/tags/<tag>, and setspushed=trueonly whengit push --porcelainreports a newly created ref (an already-existing tag does not trigger a second release).releasejob is gated onpushed == 'true'instead of ccv's "would create" output.workflow_dispatchinputdry_run: computes and reports the version (including majors) without pushing.concurrency(no cancel),timeout-minutes,shell: bash, explicitpersist-credentials: true, permission comments.CONTRIBUTING.mdHow was it tested?
actionlintclean;zizmor --persona=pedanticonly reportsself-repository(low, out of scope);pre-commit run --all-filespasses.fix:→ patch tag pushed, release called exactly once (no extra push-triggered release)feat:→ minor tagfeat!:→ run fails, no major tag on remote; a laterfix:is still blockedfeat!:withdry_run→ succeeds and reports the major versiongit tag vX.0.0 <sha>+ push) →release.yamlruns via tag pushChecklist
check.yaml— lint + test matrix)