Skip to content

fix(release-pr): only propose worthwhile releases - #84

Merged
michen00 merged 1 commit into
mainfrom
ci/release-pr-worthiness-gate
Sep 15, 2026
Merged

michen00 merged 1 commit into
mainfrom
ci/release-pr-worthiness-gate

Conversation

@michen00

@michen00 michen00 commented Sep 15, 2026

Copy link
Copy Markdown
Owner

Follow-up to #81, which I got wrong. The push trigger as merged opens a release PR on nearly every merge to main — including both bot streams.

The mistake

I claimed in #81 that "git-cliff bumps only for feat, fix and breaking changes". That came from testing git cliff --bumped-version <range>, which is not the form the workflow runs. Re-tested the way the workflow actually invokes it — bare, against a checked-out HEAD — every conventional type bumps at least the patch:

Commit Bump from v0.1.1
chore:, docs:, build:, ci:, style:, test:, refactor:, perf:, revert:, fix: v0.1.2
feat:, any !:, any BREAKING CHANGE: footer on any type v0.2.0
chore(deps), chore: merge, docs(blame) — the skip rules in cliff.toml no bump

The two bot streams land outside those skip rules: the weekly chore: autoupdate pre-commit hooks is matched by no rule, and Dependabot here commits as build(deps): while the only deps skip is ^chore\(deps.*\). Both would have proposed their own release.

The fix

A speculative run now needs a reason beyond the version having moved. It gates on the group cliff.toml parsed each commit into — features (00), fixes (01), performance (02), reverts (10) — plus anything marked breaking.

The verdict comes from git cliff --unreleased --context, so the skip rules and the breaking-change detection are not reimplemented here; cliff.toml remains the only parser. Groups are matched on the ordering index rather than the label, which carries an emoji. Changing that set is a one-line policy change.

Two details worth calling out:

  • --unreleased --context returns the previous release alongside the unreleased one. Counting both leaves the gate permanently open, so the null-version entry has to be selected. Caught in testing.
  • A manual dispatch skips the gate entirely. A human asking for a release has already supplied the reason, and refusing a chore-only release they explicitly asked for would be wrong.

Everything outside the release-worthy set still reaches the changelog and still bumps the version. It just does not, on its own, propose a release.

Test plan

The resolve script was extracted from the YAML and executed, not reasoned about.

  • Gate closedchore alone; build(deps) alone; and a batch of chore+build+docs+ci+test+refactor. All exit 0 with release=false.
  • Gate openfix among chores; feat; perf; revert; refactor!:; and refactor: with a BREAKING CHANGE: footer. The last two matter: both are breaking changes on a type outside the listed groups, and a naive type-prefix filter would have silently dropped them.
  • Unaffected paths re-verified — dispatch with chore-only commits still proceeds; the in-flight guard still fires; malformed and pinned version inputs behave as before.
  • Counted against real history on this branch: 3 release-worthy commits, which matches the three fix commits unreleased since v0.1.1.
  • actionlint clean, make check green.

Merge order

Worth merging before #83. Both changes are fix, so the version stays v0.1.2 and #83 updates in place to cover both. Merging #83 first cuts v0.1.2, and this would then propose v0.1.3 on its own.

🤖 Generated with Claude Code

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Gate speculative release PRs on release-worthy commits

🐞 Bug fix ⚙️ Configuration changes 📝 Documentation 🕐 10-20 Minutes

Grey Divider

AI Description

• Gate push-triggered release PRs on feature, fix, performance, revert, or breaking commits.
• Exclude previous-release context when evaluating unreleased commits.
• Let manual release requests bypass worthiness checks and document the policy.
Diagram

graph TD
  A["Release trigger"] --> B{"Manual dispatch?"}
  B -- Yes --> F["Propose release PR"]
  B -- No --> C["git-cliff context"] --> D["Unreleased commits"] --> E{"Worthy commit?"}
  E -- Yes --> F
  E -- No --> G["Stop quietly"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Restrict git-cliff version bumps
  • ➕ Could use version movement itself as the release signal.
  • ➕ Would remove the separate jq worthiness query.
  • ➖ Would change the project’s versioning semantics for chore, documentation, build, and test commits.
  • ➖ Would prevent non-triggering commits from contributing to the eventual version bump as currently intended.
2. Parse conventional commits directly
  • ➕ Could express release policy without depending on git-cliff context output.
  • ➕ Might make the accepted commit types visually explicit in shell logic.
  • ➖ Would duplicate cliff.toml parsing and skip rules.
  • ➖ Could diverge from git-cliff’s breaking-change detection and grouping behavior.

Recommendation: Keep the proposed git-cliff context gate. It preserves existing version and changelog behavior, treats cliff.toml as the single commit parser, and cleanly separates speculative pushes from explicit manual release requests. Reviewers should specifically confirm the null-version selection and group-index contract with the installed git-cliff version.

Files changed (2) +49 / -11

Bug fix (1) +40 / -6
release-pr.ymlGate speculative releases on git-cliff commit classifications +40/-6

Gate speculative releases on git-cliff commit classifications

• Push-triggered runs now inspect the null-version entry from 'git cliff --unreleased --context' and continue only when it contains a feature, fix, performance change, revert, or breaking commit. Manual dispatches bypass the gate, while unworthy pushes stop successfully with 'release=false'.

.github/workflows/release-pr.yml

Documentation (1) +9 / -5
CONTRIBUTING.mdDocument release worthiness and manual bypass behavior +9/-5

Document release worthiness and manual bypass behavior

• Corrects the release documentation to explain that every conventional commit type can bump the version. Documents which git-cliff groups trigger automated release PRs and that manual runs bypass the worthiness policy.

CONTRIBUTING.md

@codecov-commenter

codecov-commenter commented Sep 15, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 47.93%. Comparing base (cfd9bf4) to head (4771153).

Additional details and impacted files
@@           Coverage Diff           @@
##             main      #84   +/-   ##
=======================================
  Coverage   47.93%   47.93%           
=======================================
  Files          31       31           
  Lines        1863     1863           
=======================================
  Hits          893      893           
  Misses        970      970           
Flag Coverage Δ
integration 1.05% <ø> (ø)
unit 47.26% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider

Great, no issues found!

Qodo reviewed your code and found no material issues that require review

Grey Divider

Tip of the day
💡 Did you know, you can reply 'qodo' on any finding to push back, ask questions, or dig deeper

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Every conventional type bumps at least the patch version, so the
inferred version alone does not tell a fix apart from the weekly
hook autoupdate. As first written the trigger would have opened a
release PR on nearly every merge to main, both bot streams
included: `chore: autoupdate pre-commit hooks` and `build(deps):
bump ...` match no skip rule in cliff.toml, and both bump.

A speculative run now also needs at least one commit that
cliff.toml grouped as a feature, fix, performance change or
revert, or that it marked breaking. That verdict comes from
`git cliff --context`, so the skip rules and the breaking-change
detection are not reimplemented here. Selecting the null-version
entry matters: --unreleased returns the previous release too, and
counting it would leave the gate permanently open.

A manual dispatch skips the gate. A human asking for a release has
already supplied the reason.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@michen00
michen00 force-pushed the ci/release-pr-worthiness-gate branch from 0e6e66f to 4771153 Compare September 15, 2026 08:53
@michen00
michen00 merged commit a7cff17 into main Sep 15, 2026
12 checks passed
@michen00
michen00 deleted the ci/release-pr-worthiness-gate branch September 15, 2026 09:07
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.

2 participants