Skip to content

[CI] Restrict stable release workflows to vX.Y.Z and vX.Y.Z.postN tags - #4607

Open
pjdurden wants to merge 4 commits into
kvcache-ai:mainfrom
pjdurden:fix/4584
Open

pjdurden wants to merge 4 commits into
kvcache-ai:mainfrom
pjdurden:fix/4584

Conversation

@pjdurden

@pjdurden pjdurden commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Issue #4584: rc tags without a hyphen were published to PyPI as stable

1. Root cause

Every stable release workflow (release*.yaml) triggered on tags: ['v*'].
The only thing keeping pre-releases out was the job guard
!contains(github.ref_name, '-'), which works for semver-style tags
(v0.3.14-rc1) but not for PEP 440 tags such as v0.3.14rc1. Those have no
hyphen, so they went through the stable build-and-publish path to production
PyPI. Meanwhile pre-release.yaml only matched hyphenated tags (v*-rc*,
etc.), so a v0.3.14rc1 tag never reached the TestPyPI pre-release pipeline.

The manual workflow_dispatch backfill checks in release-musa.yaml,
release-rocm.yaml and release-npu.yaml used the same "no hyphen" test, so
they had the same gap.

2. Fix

  • All nine stable release workflows now trigger only on explicit stable tag
    patterns:
    v[0-9]+.[0-9]+.[0-9]+ and v[0-9]+.[0-9]+.[0-9]+.post[0-9]+. Post
    releases are still allowed because the project ships them (for example
    0.3.12.post1). Any tag with a pre-release, dev or local suffix no longer
    starts these workflows. I left the existing !contains(..., '-') guards in
    place; they are now redundant but do no harm.
  • The dispatch backfill validation in MUSA and ROCm now uses the bash regex
    ^v[0-9]+\.[0-9]+\.[0-9]+(\.post[0-9]+)?$ instead of the hyphen test.
  • NPU had no validation step, and its if: expression can't run a regex. I
    added a Validate release tag step to its build job using the same regex.
    publish-release depends on build, so a rejected tag also blocks the
    publish.
  • pre-release.yaml tag filters are relaxed from v*-rc* / v*-alpha* /
    v*-beta* / v*-pre* to v*rc* / v*alpha* / v*beta* / v*pre*. The
    new patterns cover everything the old ones did, and v0.3.14rc1 now goes
    to the TestPyPI flow. testpypi_wheel_gate.py normalize-version already
    accepts the hyphen-free forms (checked: v0.3.14rc1 -> 0.3.14rc1).

I chose a positive allowlist for stable tags over adding more contains()
checks to the guards. This keeps the stable path from depending on a list of
every possible pre-release spelling.

3. Files changed

  • .github/workflows/release.yaml, release-cuda13.yaml, release-efa.yaml,
    release-efa-cuda13.yaml, release-efa-non-cuda.yaml,
    release-non-cuda.yaml: tag filter.
  • .github/workflows/release-musa.yaml, release-rocm.yaml: tag filter and
    dispatch validation regex.
  • .github/workflows/release-npu.yaml: tag filter and a new
    Validate release tag step.
  • .github/workflows/pre-release.yaml: broader pre-release tag filters.
  • scripts/ci/tests/test_release_tag_filters.py (new): turns each workflow's
    GitHub tag filters into regexes and checks which tags match.

4. Risk / uncertainty

  • The stable path now only accepts vX.Y.Z and vX.Y.Z.postN. Any other
    stable tag shape, such as a four-part v1.2.3.4 or .postN written another
    way, would no longer trigger a release. I found no such tags in docs or
    history, but this shallow clone has no tags, so I couldn't list the
    upstream tag history to confirm.
  • Short PEP 440 forms such as v0.3.14a1 / v0.3.14b1 / .dev1 are now
    blocked from stable release, but they do not trigger pre-release.yaml
    either. That only matters if the project starts using those spellings.
  • The test's GitHub filter translator covers the syntax these workflows use
    (*, **, +, ?, [...]). It is not a full reimplementation of
    GitHub's matcher.

5. Verification

  • python3 -m unittest scripts/ci/tests/test_release_tag_filters.py: passes
    with the change. Against the original workflows (stashed) it fails, with
    the rc tags matching the stable workflows and missing pre-release.
  • python3 -m unittest discover -s scripts/ci/tests: all 20 tests pass.
    This is the same command CI runs in ci.yml.
  • Ran the bash regex by hand: v0.3.14 and v0.3.12.post1 are accepted;
    v0.3.14rc1 and v0.3.14-rc1 are rejected.
  • All workflow YAML files still parse. ruff format --check and
    ruff check pass on the new test. pre-commit is not installed here, so I
    did not run it.

Signed-off-by: pjdurden <prajjwalchittori1@gmail.com>
Copilot AI balanced review requested due to automatic review settings October 12, 2026 15:02

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@he-yufeng he-yufeng left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Read all twelve files against the trigger semantics and the repo's tag history. The direction is right and the gap is real: before this, v0.3.14rc1/v0.3.14a1 matched v* and passed the !contains('-') guard, so they would have taken the production PyPI path while pre-release.yaml only watched hyphenated tags.

The part I wanted to verify before saying anything: v[0-9]+.[0-9]+.[0-9]+ looks like a regex planted in a glob field, but GitHub's filter syntax does support + as "one or more of the preceding character" and [] classes, so the whitelist is valid as written, and filters are full-name matches so v0.3.14.rc1 correctly falls out of the stable set. I also checked the tag history: every stable tag ever pushed fits the whitelist (vX.Y.Z and .postN only), so nothing historical would have been orphaned.

The test's github_filter_to_regex mirrors the documented semantics (* to [^/]*, ** to .*, +? passed through as quantifiers, classes verbatim, fullmatch anchor), which is the right reference model for tag patterns; both directions are asserted. One gap to be aware of if the file evolves: ! negation is not modeled (a negated pattern would be translated as a literal !), fine today since no tag pattern uses it.

Two notes, neither blocking:

  1. After this lands, PEP 440 short forms (a1/b1/dev1/+local) and typo'd stable tags like v0.3.14post1 match no workflow at all. That flips the failure mode from publishing the wrong thing to silently skipping, which is the correct trade, and the regex-validated dispatch backfills in MUSA/ROCm/NPU are the recovery path. Worth one sentence in RELEASING.md so the next person who tags v0.3.14post1 knows why nothing ran instead of re-tagging blindly. The six workflows without a dispatch backfill have no recovery other than re-tagging; acceptable, but it makes that doc line more valuable.

  2. Heads-up on ordering with #4574: it also rewrites the release-npu.yaml tag classification (its normalize-version gate on the dispatch path), and this PR rewrites the same file's push trigger plus adds a Validate step. The two rules are consistent in outcome (hyphenated rc tags go to pre-release/TestPyPI, clean tags go to PyPI), but whichever lands second will need a rebase and a quick re-read of the combined file. May be worth sequencing them deliberately rather than letting git sort it out.

Signed-off-by: pjdurden <prajjwalchittori1@gmail.com>
@pjdurden

Copy link
Copy Markdown
Contributor Author

Thanks for checking the tag history. There is no RELEASING.md in the repo, so I put the note in the trigger comments of release.yaml and pre-release.yaml instead (b924f18): tag forms like v0.3.14a1 or v0.3.14post1 match no workflow and need a re-tag. On #4574, agreed. Happy to rebase this one and re-read release-npu.yaml if that lands first.

The red CTest job is not from this PR. Main itself is failing to build conductor_py.cpp after #4588 renamed npu to device while #4469 still uses hit.npu.

@koriyoshi2041 koriyoshi2041 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 tag-routing fix is sound, and the focused filter suite passes at b924f189. One release-integrity gap from #4584 remains: a valid stable tag is still not checked against the wheel metadata before publication.

At this head, mooncake-wheel/pyproject.toml declares 0.3.13. A push of v0.3.14 matches every new stable filter. _build-wheel.yaml sets VERSION=0.3.14, but the only step that writes that value back to pyproject.toml is guarded by inputs.version-override != ''; the normal tag-triggered workflows do not pass an override. scripts/build_wheel.sh then builds the unchanged 0.3.13 package, and _publish-wheel.yaml uploads it. The new test exercises routing only, so this mismatch remains green.

Could the stable path either fail before building when github.ref_name (normalized without v) differs from [project].version, or deliberately stamp the tag version and verify the built wheel metadata before upload? A negative test with a valid-but-mismatched tag would keep this distinct from the prerelease-classification coverage.

…ffer

A stable tag such as v0.3.14 pushed while mooncake-wheel/pyproject.toml
still says 0.3.13 built and published 0.3.13. Every stable build path now
checks the tag against pyproject.toml before building: the shared
_build-wheel (tag builds without a version override) and _build-efa-wheel
(release profile) workflows, plus the NPU, MUSA and ROCm build jobs,
including their workflow_dispatch backfills.

The test runs each workflow's check step against a matching and a
mismatched pyproject.toml.
@pjdurden

Copy link
Copy Markdown
Contributor Author

@koriyoshi2041 agreed, went with failing before the build (6392802). Each stable build path now compares the tag (minus the v) to [project].version in mooncake-wheel/pyproject.toml and stops with an error if they differ: the shared _build-wheel (tag builds with no version-override, so nightly, CI and pre-release are untouched), _build-efa-wheel on the release profile, and the NPU/MUSA/ROCm build jobs, where the dispatch backfills check the tag they checked out. ReleaseVersionCheckTest pulls that step out of each workflow and runs it against a matching and a stale pyproject, so v0.3.14 over 0.3.13 fails. Also merged main in. CTest is red on main right now (scenario.cpp after #4314, hotfix in #4729), not from this change.

@koriyoshi2041 koriyoshi2041 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.

Verified the requested follow-up at exact head 63928026.

The version guard now covers all nine stable publication paths: the six reusable-wheel callers are mapped to _build-wheel.yaml / _build-efa-wheel.yaml, while MUSA, NPU, and ROCm carry the check in their build jobs. I independently ran python3 -m unittest scripts.ci.tests.test_release_tag_filters -v: all 5 tests pass, including matching final/post-release tags and stale pyproject.toml negatives. git diff --check origin/main...HEAD also passes. This closes the release-integrity gap from my earlier review: a valid stable tag can no longer silently publish wheel metadata from a stale project version.

One correction to the CI diagnosis, non-blocking for this patch: in run 38933536186 the CTest job does not reach scenario.cpp. Its first compilation (crc_checksum.cpp.o) fails because sccache cannot start after the Actions cache endpoint returns HTTP 503 ServerBusy / Egress is over the account limit. The CI Gate failure is downstream of that infrastructure error, not a source failure from this PR.

@pjdurden

Copy link
Copy Markdown
Contributor Author

right, I misread that run. CTest in 38933536186 stops at the first compile, crc_checksum.cpp.o, because sccache can't start after the Actions cache returns 503 ServerBusy, so it never reaches scenario.cpp and nothing in it comes from this branch. CI Gate is red only because of that job.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants