From 0d24f63dfdfc0b76b48a9ac5169c74d98cef7603 Mon Sep 17 00:00:00 2001 From: Josh Wulf Date: Fri, 17 Apr 2026 16:04:56 +1200 Subject: [PATCH] docs: document seed-tag requirement for new stable branch cutover The previous RELEASE.md procedure incorrectly claimed that a BREAKING CHANGE commit after the latest dev tag would produce the correct stable major version. python-semantic-release actually ignores prerelease tags when computing the next stable version and walks back to the most recent non-prerelease tag. This caused the stable/9 cutover to compute 2.0.0 (against legacy v1.1.3 tag) instead of 9.0.0, failing the Validate version matches stable branch gate. - RELEASE.md: explain the seed-tag requirement and add a recovery section - publish.yml: replace the misleading 'use BREAKING CHANGE' hint with an actionable diagnostic that names the baseline tag PSR used and the specific recovery commands --- .github/workflows/publish.yml | 29 ++++++++++++++++++++++++++--- RELEASE.md | 30 +++++++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 4 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 6a8b1e12..9e1046fa 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -221,9 +221,32 @@ jobs: NEW_VERSION="${{ steps.check_release.outputs.new_version }}" if [[ ! "$NEW_VERSION" =~ ^${BRANCH_MAJOR}\. ]]; then - echo "::error::Version $NEW_VERSION doesn't match branch constraint ${BRANCH_MAJOR}.x" - echo "Stable branches should only receive patch and minor releases." - echo "Use a BREAKING CHANGE footer on main to bump the major version." + # Find the latest non-prerelease tag PSR used as baseline. + LATEST_STABLE_TAG=$(git tag --list 'v*' --merged HEAD \ + | grep -v -- '-dev' \ + | grep -v -- '.dev' \ + | sort -V \ + | tail -1 || true) + + echo "::error::Computed version $NEW_VERSION does not match branch constraint ${BRANCH_MAJOR}.x" + echo "" + echo "python-semantic-release computes the next stable version from the latest" + echo "non-prerelease tag reachable from HEAD. Prerelease tags (vX.Y.Z-dev.N) are ignored." + echo "" + echo "Latest non-prerelease tag PSR used as baseline: ${LATEST_STABLE_TAG:-}" + echo "" + echo "Likely causes:" + echo " 1. No seed tag exists for the previous major (expected v$((BRANCH_MAJOR-1)).x.y)." + echo " → Create one at the last v${BRANCH_MAJOR}.0.0-dev.N commit and push it:" + echo " git tag v$((BRANCH_MAJOR-1)).0.0 " + echo " git push origin v$((BRANCH_MAJOR-1)).0.0" + echo " Then re-run this workflow." + echo "" + echo " 2. Legacy non-prerelease tags from an unrelated previous package are" + echo " shadowing the seed. Delete them (after confirming with maintainers):" + echo " git push origin :refs/tags/" + echo "" + echo "See RELEASE.md → 'Creating a New Stable Branch' for the full procedure." exit 1 fi diff --git a/RELEASE.md b/RELEASE.md index e7186805..ff1f3c6a 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -125,10 +125,38 @@ BREAKING CHANGE: SDK major version bumped from 9 to 10 to track Camunda server 8 git push -u origin stable/10 ``` -The `BREAKING CHANGE` footer is required because all the bump-triggering commits are before the latest dev tag on `main`. This commit gives PSR something to compute from: it sees `BREAKING CHANGE` after `v9.0.0-dev.N` → major bump → `10.0.0` on a non-prerelease branch. +> **Critical — seed tag required**: `python-semantic-release` computes the next stable version from the latest **non-prerelease** tag in the branch's history. Prerelease tags (`vN.Y.Z-dev.N`) are **ignored** for this calculation. If the most recent non-prerelease tag is not the immediately previous stable major (e.g. `v9.x.y` before cutting `stable/10`), PSR will walk further back in history and compute the wrong version. +> +> For example, if only `v1.x` legacy tags exist when `stable/9` is first cut, PSR applies the `BREAKING CHANGE` bump against `1.1.3` and produces `2.0.0` — which fails the `Validate version matches stable branch` gate. +> +> **Before pushing the stable branch**, confirm that a `v.x.y` non-prerelease tag exists: +> +> ```bash +> git tag --list 'v*' | grep -v -- '-dev' | sort -V | tail -5 +> ``` +> +> If no suitable seed tag exists (e.g. first-time stable cutover, or prior history only has unrelated legacy tags), create one pointing at the last dev-release commit of the previous line before pushing `stable/N`: +> +> ```bash +> # Example: seed v9.0.0 at the v9.0.0-dev.N commit before cutting stable/10 +> git tag v9.0.0 +> git push origin v9.0.0 +> ``` CI runs automatically on push and publishes the first stable release (e.g. `10.0.0`). +#### Recovery: if the release workflow fails with a version mismatch + +Symptom: the `Validate version matches stable branch` step fails with a message like `Version 2.0.0 doesn't match branch constraint 10.x`. + +This means PSR fell back to a too-old baseline tag (see the note above). To recover: + +1. Identify the commit that should have been the previous stable (typically the last `vN-1.0.0-dev.*` commit on `main`). +2. Create and push the seed tag: `git tag vN-1.0.0 ` then `git push origin vN-1.0.0`. +3. Re-run the failed `Publish` workflow. + +Do **not** hand-stamp the new major version in `pyproject.toml` on the stable branch as a workaround — PSR still uses tags, not the pyproject version, to compute the bump, so the mismatch will recur on the next push. + ### 4. Bump `main` to the Next Major ```bash