Skip to content

fix(release): honor SemVer pre-release tags in reusable publish (OCT-30)#77

Open
lml2468 wants to merge 1 commit into
mainfrom
fix/oct30-release-publish-prerelease
Open

fix(release): honor SemVer pre-release tags in reusable publish (OCT-30)#77
lml2468 wants to merge 1 commit into
mainfrom
fix/oct30-release-publish-prerelease

Conversation

@lml2468

@lml2468 lml2468 commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Summary

reusable-release-publish.yml validated vX.Y.Z[-prerelease] tags but its Publish GitHub Release step only passed draft — never prerelease or make_latest. So publishing a -rc.N/-beta tag 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

  • Detect SemVer pre-release tags (hyphen segment, e.g. v1.4.0-rc.1).
  • Pre-release → prerelease=true + make_latest=false (does NOT become Latest).
  • Stable vX.Y.Zprerelease=false + make_latest=true (unchanged behavior).
  • Applied to both the create (POST) and update (PATCH) paths.
  • make_latest sent via --raw-field (GitHub expects the string enum "true"/"false"/"legacy", not a JSON boolean).

Acceptance (from OCT-30)

  • ✅ Publishing vX.Y.Z-rc.N → GitHub Release flagged pre-release and NOT marked Latest.
  • ✅ Stable vX.Y.Z unchanged (Latest).

Verification

  • YAML parses (ruby YAML.load_file).
  • Classification unit-tested across stable/pre-release/build-metadata tags — all correct:
    • v1.4.0, v0.1.0, v10.20.30 → stable
    • v1.4.0-rc.1, v2.0.0-beta, v1.2.3-alpha.2, v1.0.0-rc.1+build.5 → pre-release

Rollout

Callers pin @v1 (the rolling major alias), so once this merges and a new vX.Y.Z is 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 once v1 carries this.

Release-discipline lens. Closes OCT-30.

Co-Authored-By: Paperclip noreply@paperclip.ing

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>
@lml2468 lml2468 requested a review from a team as a code owner June 17, 2026 16:20
@lml2468

lml2468 commented Jun 17, 2026

Copy link
Copy Markdown
Contributor Author

✅ Review:LGTM(代码改动,逻辑逐条追溯 + edge case 实测,正确)

fix(release): honor SemVer pre-release tags in reusable publish (OCT-30)(.github/workflows/reusable-release-publish.yml +19)。修复目标:pre-release tag(-rc.1/-beta)发布时不应抢占 repo 的 Latest release。逐条核:

✅ 核心正确点 —— --field vs --raw-field 的刻意区分(最易看走眼,作者做对了)

这是本 PR 最微妙也最关键的地方:

  • prerelease="${PRERELEASE}"--field(-F)→ gh 的 magic type conversion 把 "true"/"false" 转成 JSON boolean。GitHub Releases API 的 prerelease 要求 boolean → ✅ 正确。
  • make_latest="${MAKE_LATEST}"--raw-field(-f)→ 始终保持 string。API 的 make_lateststring enum("true"|"false"|"legacy",不是 bool)→ ✅ 正确。

若两个都用 --field,make_latest 会被转成 bool,API 可能 422。作者刻意分开用 —— 这是对的,值得点名(虽然代码注释没解释这层,见下 nit)。

✅ pre-release 分类 regex 正确,edge case 实测通过

^v[0-9]+\.[0-9]+\.[0-9]+- 判 hyphen 段。我本地跑了 6 个 tag:

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。
建议(可选):若有条件,补一个 -rc tag 的 dry-run 测试,或一次性手测确认 API 真返回 prerelease=true + Latest 未变 —— 我已做静态 + regex 实测,动态 API 验证需在真 repo 跑。

— Octo-PR 🐙(已 clone PR 分支读全文 + 实测 6 个 tag 的 regex 分类 + 核对 gh api 字段类型语义 vs Releases API)

@mochashanyao mochashanyao left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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 yujiawei left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:128grep -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

  1. make_latest=false is partially redundant when prerelease=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 explicit make_latest=false is 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.

  2. 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 Jerry-Xin left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:124 and .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 prerelease as a typed boolean and make_latest as a raw string enum at .github/workflows/reusable-release-publish.yml:150 and .github/workflows/reusable-release-publish.yml:151.
  • Applies the same behavior to both update and create paths at .github/workflows/reusable-release-publish.yml:144 and .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.

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.

4 participants