Skip to content

fix: derive release kind from the tag, not a caller-supplied PR body field - #221

Merged
romac merged 1 commit into
circlefin:mainfrom
sdaveas-circle:fix/finalize-release-kind-from-tag
Jul 28, 2026
Merged

fix: derive release kind from the tag, not a caller-supplied PR body field#221
romac merged 1 commit into
circlefin:mainfrom
sdaveas-circle:fix/finalize-release-kind-from-tag

Conversation

@sdaveas-circle

@sdaveas-circle sdaveas-circle commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Summary

  • finalize-release.sh read Release kind from the sync PR body, defaulting to patch when missing and only ever promoting patch -> minor when PATCH==0.
  • Automated sync PRs no longer include a Release kind field in the body, so every release PR now silently classifies as patch/minor-by-promotion, misclassifying major (X.0.0) releases as minor.
  • Derives EFFECTIVE_RELEASE_KIND purely from the tag's MAJOR/MINOR/PATCH fields instead of trusting a PR-body label at all.
  • No functional impact today (branch/tag creation for minor/major was already identical code, and nothing currently consumes the release_kind output), but it was reporting the wrong value and would misfire silently if anything starts consuming that output (changelog generation, notifications, etc.).

Changes

  • .github/scripts/finalize-release.sh: remove Release kind PR-body parsing/validation; derive release kind from tag shape; guard main "$@" behind a BASH_SOURCE check so the parsing logic is sourceable for tests.
  • .github/scripts/test-finalize-release.sh (new): covers patch/minor/major derivation from the tag alone (no Release kind field in the test PR bodies) and the base-branch validation for major releases.

Test plan

  • bash .github/scripts/test-finalize-release.sh passes locally
  • shellcheck .github/scripts/finalize-release.sh .github/scripts/test-finalize-release.sh clean
  • Public CI (ci.yml) on this PR

…field

finalize-release.sh read "Release kind" from the Copybara sync PR body,
defaulting to patch when missing and only ever promoting patch->minor when
PATCH==0. The private circle-chain-reth repo's public-release-handoff
workflow just dropped that field from the PR body entirely (its own
release_kind input had the same drift problem: a caller-supplied kind could
disagree with the tag it described), so every release PR now silently
classifies as patch/minor-by-promotion, misclassifying major (X.0.0) releases
as minor.

Derive EFFECTIVE_RELEASE_KIND directly from the tag's MAJOR/MINOR/PATCH
fields instead, matching the fix already applied on the private side. Also
guards `main "$@"` behind a BASH_SOURCE check so the parsing logic can be
sourced and unit tested, and adds test-finalize-release.sh covering the
patch/minor/major derivation and the base-branch validation.
@osr21

osr21 commented Jul 28, 2026

Copy link
Copy Markdown

The fix is correct and the motivation is well-explained. A few observations after reading through both files.


Derivation logic — decision table

PATCH MINOR derived kind correct?
≠ 0 any patch ✓ (X.Y.Z, Z≠0)
= 0 = 0 major ✓ (X.0.0)
= 0 ≠ 0 minor ✓ (X.Y.0, Y≠0)

All three cases are mutually exclusive and exhaustive given the regex guard ^([0-9]+)\.([0-9]+)\.([0-9]+)$ earlier in the function. The removed explicit checks in the old case arms ([[ "${PATCH}" == "0" ]] for minor, [[ "${MINOR}" == "0" && "${PATCH}" == "0" ]] for major) were vacuously true given the new derivation — removing them is correct.


Two missing test cases

The new case block has three arms. Tests cover the happy path for each arm and one rejection (major targeting its own release branch). Two rejections aren't tested:

1. Patch release targeting main instead of the release branch

test_patch_rejects_main_base() {
  PR_BODY="$(sync_body v0.7.3 release/0.7)"
  PR_BASE_REF="main"
  PR_HEAD_REF="sync/v0_7_3"
  assert_dies "patch release PR targeting main must fail" parse_release_metadata
}

2. Minor release targeting its own release branch instead of main

test_minor_rejects_release_branch_base() {
  PR_BODY="$(sync_body v0.8.0 release/0.8)"
  PR_BASE_REF="release/0.8"
  PR_HEAD_REF="sync/v0_8_0"
  assert_dies "minor release PR targeting release branch instead of main must fail" parse_release_metadata
}

The latter matters specifically because the original bug involved a minor/major classification error — the base-branch check is the guard that would have caught a misclassified major release reaching the wrong base. Having it exercised for minor as well would close the loop.


release_kind step output is now always correct

write_outputs unconditionally writes release_kind=${EFFECTIVE_RELEASE_KIND} to GITHUB_OUTPUT. Under the old code, a PR body missing Release kind: would silently default to patch, so the output was wrong for major releases (the exact failure mode described). The new code makes it impossible for the output to disagree with the tag. This is an improvement even though nothing downstream currently reads it — the field is now trustworthy when something eventually does.


readonly + source interaction (minor, future-proofing)

finalize-release.sh declares its constants with readonly at the top level. When test-finalize-release.sh sources it, those declarations (NAMESPACE, TAG_PREFIX, RELEASE_BRANCH_PREFIX, MAIN_BRANCH) become readonly in the test shell's global scope for the lifetime of the test process. The current tests don't need to override them, so this is fine today. If a future test wanted to exercise different branch prefix conventions, it would need a subshell or a separate invocation. Worth keeping in mind if the test suite grows.


note removal — not a regression

The old note "Release kind promoted from patch to minor..." fired on a derived state that no longer exists (there's no "promotion" concept now). The validate mode still emits note "Validated ${NAMESPACE} ${EFFECTIVE_RELEASE_KIND} release ${TAG}", so the final derived kind is still observable in CI logs. No observability regression.


The two missing test cases are the only substantive gap. The fix itself is sound.

@romac
romac merged commit 90d71dc into circlefin:main Jul 28, 2026
19 checks passed
@github-actions github-actions Bot added the pending-import Merged PR awaiting reverse-sync to upstream label Jul 28, 2026
@circle-github-action-bot circle-github-action-bot added import-skipped Import skipped (no-op diff or already on target) and removed pending-import Merged PR awaiting reverse-sync to upstream labels Jul 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

import-skipped Import skipped (no-op diff or already on target)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants