Skip to content

ci: auto patch-release on every merge to main#27

Merged
gaurav0107 merged 3 commits into
mainfrom
auto-release-on-merge
Jul 12, 2026
Merged

ci: auto patch-release on every merge to main#27
gaurav0107 merged 3 commits into
mainfrom
auto-release-on-merge

Conversation

@gaurav0107

@gaurav0107 gaurav0107 commented Jul 12, 2026

Copy link
Copy Markdown
Owner

Every push to main now cuts a release automatically. Replaces release-tag.yml (which only tagged a manually bumped version) with release.yml.

Behavior

On push to main, release.yml:

  1. Reads the current version and asserts the three manifests agree (prepare_release.sh --current exits 10, not a version, on drift — so a mismatch fails loudly instead of tagging a bad release).
  2. Decides the target version:
    • vCUR already tagged (the normal merge case) → bump the patch.
    • vCUR not tagged (the merge already carried a new version — a manual minor/major bump, or the first release) → release it as-is, don't patch past it.
  3. Writes the version to all three manifests, promotes the CHANGELOG ## [Unreleased] section into a dated ## [X.Y.Z] section (and opens a fresh empty [Unreleased]), commits chore(release): vX.Y.Z back to main.
  4. Tags vX.Y.Z and 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 with GITHUB_TOKEN do 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: release serializes 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

  • Full suite: 57/57 pass (56 existing + the new test).
  • Simulated the workflow end-to-end against a copy of the repo: 0.6.2 (tagged) → bumps to 0.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

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Automated patch releases now run on every push to main, synchronizing versions across plugin manifests, promoting the Unreleased changelog section, and publishing tagged GitHub Releases.
    • Release notes are generated from the matching changelog entry, with logic to prevent self-triggering and to auto-bump when a tag already exists.
  • Documentation
    • Updated contribution/release guidance to explain automated patch vs manual minor/major release flows, including how merge requirements on main work.
  • Tests
    • Added unit test coverage for version validation, changelog promotion, idempotency, and robust error handling.

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>
@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a reusable release-preparation script and tests, replaces the prior release-tag workflow with automated releases on pushes to main, and updates release documentation and changelog guidance.

Changes

Release automation

Layer / File(s) Summary
Release preparation and validation
scripts/release/prepare_release.sh, tests/scripts/test_prepare_release.sh
Validates matching manifest versions, updates all three manifests, promotes [Unreleased] changelog content, supports semver and date options, and tests success, idempotency, and error cases.
Push-triggered release orchestration
.github/workflows/release.yml, CHANGELOG.md, CONTRIBUTING.md
Runs releases on pushes to main, skips automated release commits, bumps tagged versions, commits changes, extracts notes, creates tags and GitHub Releases, and documents release and branch-protection behavior.

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
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly captures the main change: automating patch releases on main, which matches the new release workflow and related release docs.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch auto-release-on-merge

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
tests/scripts/test_prepare_release.sh (1)

70-72: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Only 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 on prepare_release.sh lines 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 | 🔵 Trivial

Direct push to main assumes no branch protection blocks the bot.

git push origin HEAD:main will fail if main requires PR review or status checks that the GITHUB_TOKEN push 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 if main protection 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

📥 Commits

Reviewing files that changed from the base of the PR and between 8a9a0b1 and b360084.

📒 Files selected for processing (6)
  • .github/workflows/release-tag.yml
  • .github/workflows/release.yml
  • CHANGELOG.md
  • CONTRIBUTING.md
  • scripts/release/prepare_release.sh
  • tests/scripts/test_prepare_release.sh
💤 Files with no reviewable changes (1)
  • .github/workflows/release-tag.yml

Comment thread .github/workflows/release.yml
Comment thread scripts/release/prepare_release.sh
Comment thread scripts/release/prepare_release.sh Outdated
…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>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between b360084 and d5bc2dc.

📒 Files selected for processing (3)
  • .github/workflows/release.yml
  • scripts/release/prepare_release.sh
  • tests/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

Comment thread tests/scripts/test_prepare_release.sh
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>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 win

Do not persist the admin PAT in checkout
actions/checkout will write this token into the local git config, and this workflow runs repository-controlled scripts before the push. Set persist-credentials: false and 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 win

Don't treat an existing tag as a published release
If gh release create fails after the tag is pushed, or if the tag is created manually, this branch exits 0 and never creates the GitHub Release. Check gh 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

📥 Commits

Reviewing files that changed from the base of the PR and between d5bc2dc and 37c32f5.

📒 Files selected for processing (3)
  • .github/workflows/release.yml
  • CHANGELOG.md
  • CONTRIBUTING.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • CONTRIBUTING.md

Comment thread CHANGELOG.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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 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.

@gaurav0107 gaurav0107 merged commit 9f7edf4 into main Jul 12, 2026
3 checks passed
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