ci: auto patch-release on every merge to main#27
Conversation
Replace release-tag.yml (which only tagged a manually-bumped version) with release.yml, which cuts a release on every push to main: - ordinary merge -> bump the patch across all three manifests, promote the CHANGELOG [Unreleased] section into a dated one, commit the bump back to main, then tag + publish the GitHub Release. - merge that already carries a new version (a manual minor/major bump) -> release it as-is rather than patching past it, detected by whether vCUR is already tagged. The version-write + CHANGELOG promotion is extracted to scripts/release/prepare_release.sh per CONTRIBUTING (more than a couple lines of bash/jq -> a tested script), covered by tests/scripts/test_prepare_release.sh (manifest agreement, semver validation, changelog promotion + idempotency, exit-10 config-error contract). Loop-safe: the bump commit is pushed with GITHUB_TOKEN, and GITHUB_TOKEN pushes do not trigger new workflow runs; a startsWith(chore(release):) guard covers a PAT. Update CONTRIBUTING and CHANGELOG to document the automation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughAdds a reusable release-preparation script and tests, replaces the prior release-tag workflow with automated releases on pushes to ChangesRelease automation
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant PushToMain
participant GitHubActions
participant PrepareRelease
participant GitRepository
participant GitHubRelease
PushToMain->>GitHubActions: Trigger release workflow
GitHubActions->>PrepareRelease: Read or set release version
PrepareRelease->>GitRepository: Update manifests and CHANGELOG.md
GitHubActions->>GitRepository: Commit and push release changes
GitHubActions->>GitHubRelease: Create tag and publish release notes
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (2)
tests/scripts/test_prepare_release.sh (1)
70-72: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winOnly one malformed-input case is exercised.
The bad-semver test only checks
1.2(too few dots) fails, which the current glob pattern already rejects. It doesn't cover the values that actually slip through the loose pattern (e.g.1.2.3.4,1.2.3-rc1) — see the companion comment onprepare_release.shlines 77-80. Once that validation is tightened, extend this block to assert those also return 10.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/scripts/test_prepare_release.sh` around lines 70 - 72, Extend the bad-semver test around run --set in test_prepare_release.sh to also exercise values accepted by the loose pattern, such as 1.2.3.4 and 1.2.3-rc1, and assert each returns status 10. Keep the existing 1.2 case and failure-reporting style intact..github/workflows/release.yml (1)
61-75: 🩺 Stability & Availability | 🔵 TrivialDirect push to
mainassumes no branch protection blocks the bot.
git push origin HEAD:mainwill fail ifmainrequires PR review or status checks that theGITHUB_TOKENpush can't satisfy, silently breaking every release run. Worth confirming the branch protection rules explicitly allow this bot's direct push (or documenting the required exception) so the failure mode is understood ifmainprotection ever tightens.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/release.yml around lines 61 - 75, Update the release workflow’s push step in the “Apply the version + CHANGELOG” job to explicitly account for branch protection requirements: verify/document that the workflow bot is permitted to push directly to main, or adjust the release flow to use the required protected-branch mechanism. Preserve the existing commit and changed-output behavior while ensuring failures caused by main protection are intentional and clearly surfaced.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/release.yml:
- Around line 61-88: Remove direct interpolation of steps.ver.outputs.new and
steps.ver.outputs.tag from the shell scripts in the prep and release-notes
steps. Pass these values through step-level env variables, then reference the
quoted shell variables in prepare_release.sh, git commit, fallback release-note,
and awk commands, preserving the existing behavior.
In `@scripts/release/prepare_release.sh`:
- Around line 77-80: Replace the glob-based semver validation in the --set
handling case statement with an anchored check that accepts only exactly three
numeric dot-separated components (X.Y.Z). Ensure values with suffixes, extra
components, or shell metacharacters are rejected while preserving the existing
die error path.
- Around line 26-44: Validate the shared manifest version format in current()
and set_version() using a common validator, accepting only semantic versions
matching X.Y.Z with numeric components. Invoke the validator after confirming
the manifests agree and before returning or writing the version, rejecting any
other value instead of propagating it.
---
Nitpick comments:
In @.github/workflows/release.yml:
- Around line 61-75: Update the release workflow’s push step in the “Apply the
version + CHANGELOG” job to explicitly account for branch protection
requirements: verify/document that the workflow bot is permitted to push
directly to main, or adjust the release flow to use the required
protected-branch mechanism. Preserve the existing commit and changed-output
behavior while ensuring failures caused by main protection are intentional and
clearly surfaced.
In `@tests/scripts/test_prepare_release.sh`:
- Around line 70-72: Extend the bad-semver test around run --set in
test_prepare_release.sh to also exercise values accepted by the loose pattern,
such as 1.2.3.4 and 1.2.3-rc1, and assert each returns status 10. Keep the
existing 1.2 case and failure-reporting style intact.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 498872a6-7a26-4592-8029-78de99b5b46e
📒 Files selected for processing (6)
.github/workflows/release-tag.yml.github/workflows/release.ymlCHANGELOG.mdCONTRIBUTING.mdscripts/release/prepare_release.shtests/scripts/test_prepare_release.sh
💤 Files with no reviewable changes (1)
- .github/workflows/release-tag.yml
…mver
- release.yml: route steps.ver.outputs.* through env: and reference "$NEW_VERSION"
/ "$TAG" in run: blocks instead of splicing ${{ }} into the script text
(template-injection / zizmor template-injection, Critical). The version can
originate from a manifest field, so this was a real code-execution surface in a
contents:write job.
- prepare_release.sh: validate the version with an anchored ^X.Y.Z$ regex
(is_semver), applied in both current() (reject a malformed manifest version
before it reaches the workflow) and set_version() (the old glob accepted
1.2.3-beta / 1.2.3.4 / "1.2.3; cmd"). Closes the second half of the injection.
- test_prepare_release.sh: cover the values the loose glob let through
(1.2.3.4, 1.2.3-rc1, 1.2.3-beta, a metacharacter payload) and a non-semver
manifest reaching --current; assert the payload has no side effect.
- release.yml: note the branch-protection requirement at the push step.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@tests/scripts/test_prepare_release.sh`:
- Around line 70-78: Extend the invalid-version loop in the --set rejection test
to snapshot all release fixture files, including manifests and CHANGELOG.md,
before each run and compare them byte-for-byte afterward. Keep the existing
exit-code and injected-file assertions, and ensure each test case starts from an
unchanged fixture state.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 1d31cc4a-c4ae-411d-aaf0-1cfdc4485d6b
📒 Files selected for processing (3)
.github/workflows/release.ymlscripts/release/prepare_release.shtests/scripts/test_prepare_release.sh
🚧 Files skipped from review as they are similar to previous changes (2)
- scripts/release/prepare_release.sh
- .github/workflows/release.yml
Protect main so a failing review or CI blocks the merge: - required status checks: suite (ubuntu-latest), suite (macos-latest) - required conversation resolution: unresolved CodeRabbit comments block merge (CodeRabbit's own check reports success even on findings, so resolution — not the check status — is the gate) - require PR, enforce_admins=false so an admin is never locked out Branch protection also blocks the release bot's push to main, so release.yml now checks out with RELEASE_TOKEN (admin PAT, Contents: write) and falls back to the default token. A PAT push retriggers workflows, so the chore(release): if-guard is now load-bearing against a bump loop. Document the setup in CONTRIBUTING + CHANGELOG. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
.github/workflows/release.yml (2)
37-44: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winDo not persist the admin PAT in checkout
actions/checkoutwill write this token into the local git config, and this workflow runs repository-controlled scripts before the push. Setpersist-credentials: falseand pass the PAT only to the dedicated push step.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/release.yml around lines 37 - 44, The actions/checkout step currently persists the release token in local Git configuration while repository scripts run. Update the checkout configuration to set persist-credentials to false, and ensure the dedicated push step receives secrets.RELEASE_TOKEN (with any existing fallback behavior preserved) explicitly instead of relying on checkout credentials.Source: Linters/SAST tools
118-121: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winDon't treat an existing tag as a published release
Ifgh release createfails after the tag is pushed, or if the tag is created manually, this branch exits 0 and never creates the GitHub Release. Checkgh release view "$TAG"separately; only skip when the release already exists.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/release.yml around lines 118 - 121, Update the existing-tag branch in the release workflow so it does not treat tag existence as proof of a published release. Check `gh release view "$TAG"` separately and exit successfully only when the GitHub Release exists; otherwise continue to the release creation step even if `git tag --list "$TAG"` finds the tag.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@CHANGELOG.md`:
- Line 10: Update the CHANGELOG entry describing automated patch releases to
state that every non-release push to main triggers a release, reflecting the
workflow’s exclusion of its own chore(release): bump commit. Keep the remaining
release behavior description unchanged.
---
Outside diff comments:
In @.github/workflows/release.yml:
- Around line 37-44: The actions/checkout step currently persists the release
token in local Git configuration while repository scripts run. Update the
checkout configuration to set persist-credentials to false, and ensure the
dedicated push step receives secrets.RELEASE_TOKEN (with any existing fallback
behavior preserved) explicitly instead of relying on checkout credentials.
- Around line 118-121: Update the existing-tag branch in the release workflow so
it does not treat tag existence as proof of a published release. Check `gh
release view "$TAG"` separately and exit successfully only when the GitHub
Release exists; otherwise continue to the release creation step even if `git tag
--list "$TAG"` finds the tag.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 8deffc4c-afa1-4629-814e-e42bae587162
📒 Files selected for processing (3)
.github/workflows/release.ymlCHANGELOG.mdCONTRIBUTING.md
🚧 Files skipped from review as they are similar to previous changes (1)
- CONTRIBUTING.md
| ## [Unreleased] | ||
|
|
||
| ### Added | ||
| - **Automated patch releases on merge (`.github/workflows/release.yml`).** Every push to `main` now cuts a release: an ordinary merge auto-bumps the patch version across all three manifests, promotes the `## [Unreleased]` CHANGELOG section into a dated section, commits the bump back to `main`, then tags and publishes the GitHub Release. A merge that already carries a new version (a manual minor/major bump) is released as-is instead of being patched past. The version-writing + CHANGELOG promotion is extracted to `scripts/release/prepare_release.sh` (covered by `tests/scripts/test_prepare_release.sh`), with strict `^X.Y.Z$` validation so a malformed manifest version can't reach the workflow. Replaces `release-tag.yml`, which only tagged manual bumps. |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Clarify that release-bot pushes are excluded.
The workflow intentionally skips its own chore(release): bump commit, so “Every push to main now cuts a release” is technically inaccurate. Say “Every non-release push” or equivalent to match the guard.
🧰 Tools
🪛 LanguageTool
[uncategorized] ~10-~10: The official name of this software platform is spelled with a capital “H”.
Context: ... - Automated patch releases on merge (.github/workflows/release.yml). Every push t...
(GITHUB)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@CHANGELOG.md` at line 10, Update the CHANGELOG entry describing automated
patch releases to state that every non-release push to main triggers a release,
reflecting the workflow’s exclusion of its own chore(release): bump commit. Keep
the remaining release behavior description unchanged.
Every push to
mainnow cuts a release automatically. Replacesrelease-tag.yml(which only tagged a manually bumped version) withrelease.yml.Behavior
On push to
main,release.yml:prepare_release.sh --currentexits10, not a version, on drift — so a mismatch fails loudly instead of tagging a bad release).## [Unreleased]section into a dated## [X.Y.Z]section (and opens a fresh empty[Unreleased]), commitschore(release): vX.Y.Zback tomain.vX.Y.Zand publishes the GitHub Release, notes taken from that CHANGELOG section.So contributors just add notes under
## [Unreleased]; they become the next release's notes. No manual patch bump needed.Why it doesn't loop
The bump commit is pushed with the default
GITHUB_TOKEN, and pushes made withGITHUB_TOKENdo not trigger new workflow runs (documented GitHub behavior). A!startsWith(head_commit.message, 'chore(release):')guard is a second belt for anyone who swaps in a PAT/app token.concurrency: group: releaseserializes runs so two quick merges can't both claim the same patch number.Structure
Per CONTRIBUTING ("more than a couple lines of bash/jq → a tested script"), the version-write + CHANGELOG promotion lives in
scripts/release/prepare_release.sh, not the YAML. The workflow only decides the target version and does the git/tag/release plumbing.tests/scripts/test_prepare_release.sh(new) covers: manifest-agreement check, semver validation, all-three-manifests bump, CHANGELOG promotion + idempotency, and the exit-10 config-error contract. Runs against a mktemp fixture tree — never touches the real manifests.Verification
0.6.2(tagged) → bumps to0.6.3, all three manifests updated,[Unreleased]promoted to## [0.6.3] — <date>with the pending installer notes moved under it, release-notes extraction returns that section.Notes / follow-ups
mainis currently unprotected, so the workflow can push the bump commit. If you add branch protection later, allow thegithub-actionsbot to bypass it (or switch the push to a PAT/app token) — otherwise the bump push is rejected.v0.6.2is tagged, so it bumps tov0.6.3and publishes it, using the current[Unreleased]notes.testsworkflow) is onmain. Merging ci: run the bash test suite on PRs + fix triage_filter jq crashes #26 first gives this PR's script CI coverage on future changes.🤖 Generated with Claude Code
Summary by CodeRabbit
main, synchronizing versions across plugin manifests, promoting theUnreleasedchangelog section, and publishing tagged GitHub Releases.mainwork.