bug(manifest-check): pre-release tags (vX.Y.Z-rc.N) falsely fail tag-vs-manifest — last-tag detection isn't semver-aware #66

Closed
opened 2026-06-25 17:19:17 +02:00 by surveyor · 0 comments
Owner

Symptom

manifest-check.sh step tag-vs-manifest fails when a pre-release tag (e.g. v0.4.0-rc.1) exists in the repo and is the closest tag to HEAD, even though the manifest correctly tracks the stable lineage:

[manifest-check] tag-vs-manifest
  FAIL: manifest 0.3.4 is BEHIND tag v0.4.0-rc.1 (manifest must be >= last tag)

Surfaced by slice 2 (PR #65): slice 3 pushed v0.4.0-rc.1 at 139f1ca as the reusable-workflow ref-tag, and the toolkit dogfoods manifest-check on its own repo. The check now reads rc.1 as "the last release tag" and flags VERSION=0.3.4 as behind. CI red → #65 blocked.

Root cause (verified at source)

scripts/manifest-check.sh line 170:

last_tag=$(git describe --tags --abbrev=0 --match 'v*' 2>/dev/null || true)

--match 'v*' matches v0.4.0-rc.1 (it begins with v), and git describe --abbrev=0 returns the closest tag by commit distance — rc.1 (at 139f1ca) is closer to HEAD than v0.3.4 (at 16c1868). So last_tag=v0.4.0-rc.1, and 0.3.4 < 0.4.0-rc.1 → FAIL. The detection has no notion that a pre-release is not a released version.

This is present in both @v0.3.1 (what #65's consumer CI pins) and HEAD — it is not fixable by bumping the pin.

Tested fix

Exclude the semver pre-release delimiter from last-tag detection:

last_tag=$(git describe --tags --abbrev=0 --match 'v*' --exclude '*-*' 2>/dev/null || true)

Verified locally against the slice-2 tree:

  • current (buggy): git describe --tags --abbrev=0 --match 'v*'v0.4.0-rc.1
  • fixed: … --exclude '*-*'v0.3.4 → matches manifest → PASS

(A git tag --sort=-v:refname | grep -v -- '-' | head -1 filter is an equivalent if --exclude portability is a concern, but --exclude is supported by the git in CI.)

Why this matters beyond the dogfood

An external consumer pinning @v0.4.0-rc.1 wouldn't have rc.1 in their tag list, so they wouldn't trip this specific case. But any consumer who creates their own pre-release tag (-rc/-beta/-alpha) on their repo would hit the identical false-failure. The fix is semver-correctness (pre-releases are not releases), not a dogfooding workaround.

Sequencing note (the knot)

Because #65's consumer manifest-check.yml pins _manifest-check.yml@v0.3.1, the fix must (a) land in manifest-check.sh, (b) ship as a new patch tag (e.g. v0.3.5, cuttable via the still-present v0.3.x mechanism on main before #65 merges), then (c) #65 re-pins manifest-check.yml @v0.3.1 → @v0.3.5. Exact path is QM/operator's call.

Refs

Filed: 2026-06-25 from Surveyor's #65 review (CI-red root-cause).

## Symptom `manifest-check.sh` step `tag-vs-manifest` fails when a pre-release tag (e.g. `v0.4.0-rc.1`) exists in the repo and is the closest tag to HEAD, even though the manifest correctly tracks the stable lineage: ``` [manifest-check] tag-vs-manifest FAIL: manifest 0.3.4 is BEHIND tag v0.4.0-rc.1 (manifest must be >= last tag) ``` Surfaced by **slice 2 (PR #65)**: slice 3 pushed `v0.4.0-rc.1` at `139f1ca` as the reusable-workflow ref-tag, and the toolkit dogfoods `manifest-check` on its own repo. The check now reads rc.1 as "the last release tag" and flags `VERSION=0.3.4` as behind. CI red → #65 blocked. ## Root cause (verified at source) `scripts/manifest-check.sh` line 170: ```bash last_tag=$(git describe --tags --abbrev=0 --match 'v*' 2>/dev/null || true) ``` `--match 'v*'` matches `v0.4.0-rc.1` (it begins with `v`), and `git describe --abbrev=0` returns the *closest tag by commit distance* — rc.1 (at 139f1ca) is closer to HEAD than v0.3.4 (at 16c1868). So `last_tag=v0.4.0-rc.1`, and `0.3.4 < 0.4.0-rc.1` → FAIL. The detection has **no notion that a pre-release is not a released version.** This is present in **both** `@v0.3.1` (what #65's consumer CI pins) and HEAD — it is not fixable by bumping the pin. ## Tested fix Exclude the semver pre-release delimiter from last-tag detection: ```bash last_tag=$(git describe --tags --abbrev=0 --match 'v*' --exclude '*-*' 2>/dev/null || true) ``` Verified locally against the slice-2 tree: - current (buggy): `git describe --tags --abbrev=0 --match 'v*'` → `v0.4.0-rc.1` - fixed: `… --exclude '*-*'` → `v0.3.4` → matches manifest → **PASS** (A `git tag --sort=-v:refname | grep -v -- '-' | head -1` filter is an equivalent if `--exclude` portability is a concern, but `--exclude` is supported by the git in CI.) ## Why this matters beyond the dogfood An **external** consumer pinning `@v0.4.0-rc.1` wouldn't have rc.1 in *their* tag list, so they wouldn't trip this specific case. But any consumer who creates their **own** pre-release tag (`-rc`/`-beta`/`-alpha`) on their repo would hit the identical false-failure. The fix is semver-correctness (pre-releases are not releases), not a dogfooding workaround. ## Sequencing note (the knot) Because #65's consumer `manifest-check.yml` pins `_manifest-check.yml@v0.3.1`, the fix must (a) land in `manifest-check.sh`, (b) ship as a new patch tag (e.g. v0.3.5, cuttable via the still-present v0.3.x mechanism on main before #65 merges), then (c) #65 re-pins `manifest-check.yml` `@v0.3.1 → @v0.3.5`. Exact path is QM/operator's call. ## Refs - Blocks: [PR #65 (slice 2)](https://git.frankenbit.de/frankenbit/release-toolkit/pulls/65) - Originating tag: slice 3 `v0.4.0-rc.1` at 139f1ca - Sibling silent-failure-class lessons: #41, #56 Filed: 2026-06-25 from Surveyor's #65 review (CI-red root-cause).
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
frankenbit/release-toolkit#66
No description provided.