fix(release): honor SemVer pre-release tags in reusable publish (OCT-30)#77
fix(release): honor SemVer pre-release tags in reusable publish (OCT-30)#77lml2468 wants to merge 1 commit into
Conversation
The Publish GitHub Release step only passed `draft`, so publishing a `vX.Y.Z-rc.N`/`-beta` tag created a normal release marked Latest — a release-discipline footgun where a pre-release silently supersedes the stable line. Classify pre-release tags (SemVer hyphen segment) and set `prerelease=true` + `make_latest=false`; stable tags get `prerelease=false` + `make_latest=true` (unchanged behavior). Mirrors the classification already used in this repo's own release.yml. Closes OCT-30. Co-Authored-By: Paperclip <noreply@paperclip.ing>
✅ Review:LGTM(代码改动,逻辑逐条追溯 + edge case 实测,正确)
✅ 核心正确点 ——
|
| tag | 判定 | 正确性 |
|---|---|---|
v1.2.3 / v2.0.0 |
stable, make_latest=true | ✅ |
v1.2.3-rc.1 / -beta |
prerelease, make_latest=false | ✅ |
v1.2.3+build.5 |
stable, make_latest=true | ✅ build metadata 非 prerelease,未误判 |
v1.2.3-rc.1+build |
prerelease, make_latest=false | ✅ + 前有 -,正确检出 |
关键:build-metadata-only tag(+build,SemVer 里 + 是 build、- 才是 prerelease)没有被误判为 pre-release —— 这是 hyphen-only 检测的正确行为。且上游 "Validate semver tag format" step 的严格 regex(已读,拒绝 leading zero / 空段 / v1 别名)保证进到这里的 $TAG 已是合法 SemVer 形状,所以注释"a hyphen here is unambiguously the pre-release delimiter"的前提成立。
✅ 两条发布路径都覆盖,无副作用
update(PATCH 已存在 release)和 create 两条路径都加了 prerelease+make_latest → 一致。stable tag 始终送 make_latest=true(维持/重设 Latest),pre-release 送 false(不抢占既有 stable Latest)—— 正是修复主旨,且对既有 stable 无回归。
🔵 nit(非阻塞,可选)
代码注释解释了"为什么 pre-release 不能当 Latest",但没解释 --field vs --raw-field 的刻意区分。这层最容易被后来者"统一成 --field"而引入 422 bug。建议加一行注释:# make_latest is a string enum (true|false|legacy), NOT bool — use --raw-field so gh does not type-convert it。纯防御性注释,非阻塞。
结论
逻辑正确、字段类型用对、edge case 实测通过、两路径覆盖一致 —— 可合并。
注:作者 @lml2468 = 我
gh账号本身,无法自审批,仅以此评论表态 LGTM。需要票请另一位 reviewer。
建议(可选):若有条件,补一个-rctag 的 dry-run 测试,或一次性手测确认 API 真返回 prerelease=true + Latest 未变 —— 我已做静态 + regex 实测,动态 API 验证需在真 repo 跑。
— Octo-PR 🐙(已 clone PR 分支读全文 + 实测 6 个 tag 的 regex 分类 + 核对 gh api 字段类型语义 vs Releases API)
mochashanyao
left a comment
There was a problem hiding this comment.
[Octo-Q · automated review]
Verdict: Approve — no blocking findings; notes below (data-flow traced).
.github PR#77 Review Report — fix(release): honor SemVer pre-release tags in reusable publish (OCT-30)
Reviewer: Octo-Q (automated review)
Head SHA: 78e467fda024499f5dda8af4ef6fc8f042862842
File: .github/workflows/reusable-release-publish.yml (+19/−0)
1. Investigation Summary (4-phase root cause)
Problem: The reusable publish workflow validated SemVer tags (including pre-release) but never passed prerelease or make_latest to the GitHub Releases API. Result: publishing v1.4.0-rc.1 created a normal release marked Latest, silently superseding the stable line.
Root cause: The gh api calls in both the PATCH (update) and POST (create) paths only sent draft and tag_name — the two fields that control release classification were absent.
Fix: Adds a shell-level pre-release classifier (grep for SemVer hyphen segment) and threads prerelease + make_latest into both API call paths.
2. Verification Conclusions
| Item | Status | Evidence |
|---|---|---|
| Pre-release regex correctness | ✅ | '^v[0-9]+\.[0-9]+\.[0-9]+-' correctly matches vX.Y.Z-* and rejects vX.Y.Z and vX.Y.Z+build. Upstream strict-semver step (line 39–47) guarantees valid shape, so the simplified classifier is unambiguous. |
| Build metadata not misclassified | ✅ | SemVer build metadata uses + not -; upstream regex allows + segment. v1.0.0+build.5 → no hyphen match → stable (correct per SemVer §10). |
--field vs --raw-field type handling |
✅ | --field prerelease="${PRERELEASE}" → gh coerces "true"/"false" to JSON boolean (GitHub API expects boolean for prerelease). --raw-field make_latest="${MAKE_LATEST}" → sends literal string "true"/"false" (GitHub API expects string enum for make_latest). Both correct. |
| Symmetry: create (POST) and update (PATCH) paths | ✅ | Both paths receive identical prerelease + make_latest fields (lines 150–151 PATCH, lines 160–161 POST). |
set -euo pipefail interaction |
✅ | grep -Eq inside if — exit code 1 (no match) is consumed by the conditional, not trapped by set -e. Standard shell idiom, correct. |
Reference parity with release.yml |
✅ | Same grep pattern '^v[0-9]+\.[0-9]+\.[0-9]+-' used in the repo's own release.yml (line 53). Logic is consistent. PR adds make_latest which release.yml omits — appropriate enhancement for the reusable variant. |
3. Findings
No P0/P1 issues found.
P2 — Minor observations (non-blocking)
P2-1: release.yml parity gap (informational)
The repo's own release.yml creates/updates releases without make_latest. While not this PR's scope, the same pre-release-as-Latest footgun exists there. Consider a follow-up to add make_latest to release.yml for full parity.
- Diff-scope: pre-existing (not introduced or amplified by this PR).
P2-2: No runtime verification in CI
The PR body describes manual YAML parse validation and classification unit tests, but no CI gate verifies the workflow file. This is standard for .github workflow repos (testing requires execution), so informational only.
- Diff-scope: pre-existing.
4. Data Flow Trace
inputs.tag (workflow_call input)
→ env:TAG (step "Publish GitHub Release", line 120)
→ upstream strict-semver validation (step "Validate semver tag format", line 39)
guarantees: TAG matches ^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-...)?(\+...)?$
→ printf '%s' "$TAG" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+-'
→ match (hyphen segment present): PRERELEASE="true", MAKE_LATEST="false"
→ no match (stable tag): PRERELEASE="false", MAKE_LATEST="true"
→ consumed by:
PATCH path (line 148–152): --field prerelease="${PRERELEASE}" --raw-field make_latest="${MAKE_LATEST}"
POST path (line 158–162): --field prerelease="${PRERELEASE}" --raw-field make_latest="${MAKE_LATEST}"
→ gh CLI sends to GitHub Releases API with correct types
All data flows verified — no empty-value, short-circuit, or gate-bypass risks.
5. R5 Blind-spot Checklist
- C1 — Dual-path parity: ✅ Clear. Both create (POST) and update (PATCH) paths receive identical field additions. Symmetric.
- C2 — Control-flow ordering / nesting: ✅ Clear. The classifier runs once at the top of the step, before the release lookup. No re-entry or double-application risk.
- C3 — Authorization boundary ≠ capability boundary: N/A. No new endpoints, tools, or auth changes. Workflow permissions unchanged (
contents: write,actions: read). - C4 — Authorization lifecycle / container-member cascade: N/A. No auth/permission changes.
6. Cross-round Blocker Recheck (R6)
N/A — first review of this PR.
7. Verdict
[Octo-Q] verdict: APPROVE
Clean, well-scoped fix. Pre-release classification logic is correct, data flow is sound, both API paths are symmetrically updated, and type handling (--field vs --raw-field) matches GitHub API expectations. No P0/P1 issues. Two P2 informational notes for follow-up.
yujiawei
left a comment
There was a problem hiding this comment.
Code Review — PR #77 (.github)
Verdict: APPROVED
A focused, correct fix. reusable-release-publish.yml previously passed only draft to the publish step, so a -rc/-beta tag would create a normal release flagged Latest — silently superseding the stable line. This PR classifies the tag and sets prerelease/make_latest accordingly on both the create (POST) and update (PATCH) paths. Reviewed at head 78e467fda024499f5dda8af4ef6fc8f042862842.
Verification
| Item | Status | Evidence |
|---|---|---|
| Pre-release classification logic | ✅ | reusable-release-publish.yml:128 — grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+-' matches a hyphen immediately after vX.Y.Z |
| Build-metadata tags not misclassified | ✅ | v1.2.3+build-info has + (not -) after the patch → stays stable; v1.0.0-rc.1+build.5 → pre-release |
| Runs after strict-SemVer validation | ✅ | The Validate semver tag format step already guarantees shape, so the looser classification regex is sound |
prerelease API type |
✅ | --field prerelease=... → gh typed conversion to JSON boolean (API expects boolean) |
make_latest API type |
✅ | --raw-field make_latest=... → string enum "true"/"false" (API rejects a JSON boolean here) |
| Applied to both POST and PATCH | ✅ | reusable-release-publish.yml:150-151 and :160-161 |
set -euo pipefail safe with grep -Eq |
✅ | The grep is in an if condition, so a no-match exit code does not abort the step |
Mirrors existing release.yml |
✅ | release.yml uses the identical classification regex ^v[0-9]+\.[0-9]+\.[0-9]+- |
| CI | ✅ | actionlint + "no tabs in workflow files" both pass |
Findings
No P0/P1 issues.
P2 — non-blocking observations
-
make_latest=falseis partially redundant whenprerelease=true. GitHub does not allow a pre-release to be the Latest release and will auto-select a new Latest when a release is flagged pre-release. So for the pre-release branch the explicitmake_latest=falseis belt-and-suspenders (harmless and arguably clearer). It is genuinely load-bearing only on the stable branch (make_latest=true), which is the part that matters — fine as written. -
PATCH path does not actively demote a pre-existing wrongly-Latest release. This is correctly out of scope: re-publishing a pre-release tag over an old release that was incorrectly Latest will set
prerelease=true, which GitHub itself uses to drop it from Latest — so even this edge resolves correctly. No action needed; noting for completeness.
Conclusion
Correct, minimal, well-documented (the --field vs --raw-field rationale in the PR body is accurate). Behavior for stable vX.Y.Z tags is unchanged; pre-release tags are now flagged and kept off Latest. Approving.
Jerry-Xin
left a comment
There was a problem hiding this comment.
The PR is in scope for Mininglamp-OSS/.github and correctly fixes release publishing behavior for SemVer pre-release tags in the reusable release workflow.
💬 Non-blocking
- 🔵 Suggestion: The pre-release classification now appears in both
.github/workflows/reusable-release-publish.yml:124and.github/workflows/release.yml:50. The duplication is small and consistent today, but if release logic grows, consider extracting or documenting a shared test snippet to keep the two workflows aligned.
✅ Highlights
- Correctly classifies
vX.Y.Z-*tags as pre-releases at.github/workflows/reusable-release-publish.yml:130. - Correctly sends
prereleaseas a typed boolean andmake_latestas a raw string enum at.github/workflows/reusable-release-publish.yml:150and.github/workflows/reusable-release-publish.yml:151. - Applies the same behavior to both update and create paths at
.github/workflows/reusable-release-publish.yml:144and.github/workflows/reusable-release-publish.yml:153. - Matches the project’s release architecture that pre-releases must not advance stable release state, documented in
docs/workflow-architecture.md:400.
Verification performed: YAML parsing succeeded, and local classification checks matched stable, pre-release, and build-metadata examples.
Summary
reusable-release-publish.ymlvalidatedvX.Y.Z[-prerelease]tags but its Publish GitHub Release step only passeddraft— neverprereleaseormake_latest. So publishing a-rc.N/-betatag created a normal release marked Latest: a release-discipline footgun where a pre-release silently supersedes the stable line.This teaches the publish step to classify pre-release tags and flag them correctly. It mirrors the classification logic already present in this repo's own
release.yml.Changes
v1.4.0-rc.1).prerelease=true+make_latest=false(does NOT become Latest).vX.Y.Z→prerelease=false+make_latest=true(unchanged behavior).make_latestsent via--raw-field(GitHub expects the string enum"true"/"false"/"legacy", not a JSON boolean).Acceptance (from OCT-30)
vX.Y.Z-rc.N→ GitHub Release flagged pre-release and NOT marked Latest.vX.Y.Zunchanged (Latest).Verification
YAML.load_file).v1.4.0,v0.1.0,v10.20.30→ stablev1.4.0-rc.1,v2.0.0-beta,v1.2.3-alpha.2,v1.0.0-rc.1+build.5→ pre-releaseRollout
Callers pin
@v1(the rolling major alias), so once this merges and a newvX.Y.Zis tagged, the alias advances and every caller picks up the fix automatically — no per-repo caller change required. Per-repo RELEASING.md docs (corrected during OCT-9) can have the "Track-1 pre-releases unsupported" caveat lifted in a follow-up oncev1carries this.Release-discipline lens. Closes OCT-30.
Co-Authored-By: Paperclip noreply@paperclip.ing