Skip to content

feat(release): add Cut Pre-release Tag workflow (#2677) - #3253

Merged
myasnikovdaniil merged 4 commits into
mainfrom
feat/cut-rc-workflow
Jul 9, 2026
Merged

myasnikovdaniil merged 4 commits into
mainfrom
feat/cut-rc-workflow

Conversation

@myasnikovdaniil

@myasnikovdaniil myasnikovdaniil commented Jul 9, 2026 •

Copy link
Copy Markdown
Contributor

What this PR does

Adds cut-prerelease.yaml — a workflow_dispatch that becomes the sole entry point for creating a pre-release tag (vX.Y.Z-alpha.N / -beta.N / -rc.N), replacing the manual git push origin HEAD:refs/tags/<tag>.

Why

#2677 makes stable tags immutable, but tag creation was still only convention-enforced for pre-releases. Repo rulesets can already restrict stable vX.Y.Z creation to CI — a "Restrict creations" ruleset targeting v* with an exclude pattern for v*-* covers stable tags without any new workflow. What that approach cannot cover is pre-releases: -rc / -alpha / -beta tags were hand-pushed, so they can't be locked to CI while they still have a human entry point.

This workflow closes that gap by making CI the sole creator of pre-release tags too. With it in place, an admin can extend "Restrict creations" (CI app as sole bypass) to all of v* — pre-release and stable alike — instead of only the stable subset.

(Correction: an earlier version of this description claimed a ruleset couldn't express "stable-only" creation. It can, via include + exclude patterns — thanks IvanHunters. The workflow's real value is extending creation-control to pre-release tags, plus the audit trail, single entry point, guaranteed base_ref, and app-identity push that makes tags.yaml fire.)

How it works

  • Generates the CozyStack CI app token (same app as promote-rc.yaml) and pushes the tag as the app — so tags.yaml fires (a tag pushed with the default GITHUB_TOKEN would not) and the push event's base_ref is populated (tags.yaml's Get base branch step requires it).
  • Validates ^v\d+\.\d+\.\d+-(alpha|beta|rc)\.\d+$ and rejects stable vX.Y.Z — stable tags are cut write-once by the promote flow at a PR merge commit, never here.
  • Gates on the dispatcher having write access to the repo (workflow_dispatch already requires it; the explicit check is auditable defense-in-depth that fails closed).
  • Enforces dispatch from main (new minor's vX.Y.0-* only) or the matching release-X.Y branch; write-once (refuses an existing tag) with a stale-tip guard so a branch that advanced since dispatch fails cleanly instead of burning the tag name.
  • Pushes HEAD:refs/tags/<tag> from the branch tip — the exact form docs/release.md prescribes. Runs on ubuntu-latest (no build runner needed).

Also updates tags.yaml's two human-facing error messages and docs/release.md (the RC and patch flows, the workflow list, and a new repo-side tag-ruleset enforcement note) to point at the workflow instead of a manual push. Completes SHA-pinning on the touched release workflows (nightly.yaml, promote-rc.yaml, retention.yaml) — the zizmor "Audit workflows" gate was red on main for those.

Rollout ordering

The two v* tag rulesets have a hard order (now documented in docs/release.md):

  1. Immutability (Restrict updates + deletions, no bypass) — safe to enable at any time; nothing in the flow ever moves or deletes a v* tag.
  2. Creation control (Restrict creations, CI app sole bypass) — enable only after this PR ships, since this workflow is what makes CI the sole creator of pre-release tags. Enabling it earlier would lock maintainers out of cutting rc/alpha/beta.

Validation

  • actionlint — clean.
  • act -l — job graph resolves (cut / workflow_dispatch).
  • zizmor --offline — no findings across the whole workflows dir (SHA-pinned actions, least-privilege contents: read).
  • Run blocks bash -n syntax-checked.
  • Untested locally: the workflow runtime (app-token auth, the real tag push, base_ref population, the getCollaboratorPermissionLevel gate) needs a live run with the CI secrets — the same "orchestration is unproven until it runs" caveat feat(release): immutable tags & rc→stable promotion (#2677) #3017 carried.

Review follow-up

Addressed the non-blocking findings from IvanHunters' review (commit fix(ci): address review findings):

  1. [MINOR] main dispatch now requires patch Z == 0 (a new minor's pre-release), so a patch-line rc can't be cut from main content.
  2. [MINOR] Added a stale-tip guard: if the dispatch branch advanced between dispatch and push, fail with a re-run hint instead of tagging a stale commit / burning the write-once name on an empty-base_ref failure.
  3. [MINOR] Corrected the PR-body rationale above (rulesets can express stable-only creation via include + exclude).
  4. [NIT] ls-remote now distinguishes "tag absent" (exit 2) from a transport/auth failure.
  5. [NIT] Shallow checkout (fetch-depth: 1, no fetch-tags); dropped the redundant local tag pre-check.
  6. [NIT] Pure-input validation now runs before the token mint + checkout (fail-fast).

Plus a write-access gate on the dispatcher, requested separately.

Part of #2677.

Release note

NONE

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added a controlled workflow to create pre-release tags (alpha/beta/rc) with write-once behavior.
    • Added stronger enforcement so pre-releases are cut from the correct release line.
  • Bug Fixes
    • Improved guidance when stable tags are rejected or when base branch info is missing.
    • Added safeguards to prevent cutting tags from an out-of-date branch tip.
  • Documentation
    • Updated release process docs, including new tag protection rulesets for v* tags.
  • Chores
    • Pinned multiple CI workflow actions to fixed versions for reliability.

Pre-release tags (vX.Y.Z-alpha.N / -beta.N / -rc.N) were cut by a manual
`git push origin HEAD:refs/tags/<tag>`. That leaves the immutable-tag work in
#2677 only partly enforceable: a repo ruleset cannot restrict `v*` tag
*creation* to CI while still letting maintainers hand-push pre-releases (fnmatch
has no way to match "v* except v*-rc.*"), so "do not hand-cut stable tags" stays
a matter of discipline backed only by an in-workflow guard.

Add cut-prerelease.yaml — a workflow_dispatch that becomes the sole entry point
for creating a pre-release tag:

- Generates the CozyStack CI app token (same app as promote-rc.yaml) and pushes
  the tag as the app, so tags.yaml fires (a tag pushed with the default
  GITHUB_TOKEN would not trigger it) and the push-event base_ref is populated
  (tags.yaml's Get-base-branch step needs it).
- Validates ^v\d+\.\d+\.\d+-(alpha|beta|rc)\.\d+$ and rejects stable vX.Y.Z —
  stable tags are cut write-once by the promote flow at a PR merge commit.
- Enforces dispatch from main or the matching release-X.Y branch, and refuses to
  move an existing tag (write-once).
- Pushes HEAD:refs/tags/<tag> from the branch tip — the exact form
  docs/release.md prescribes — on ubuntu-latest (no build runner needed).

With every pre-release cut by CI, repo admins can then enable a "Restrict
creations" tag ruleset on `v*` with the CI app as the sole bypass actor, turning
tag-creation discipline into hard enforcement.

Also update tags.yaml's two human-facing error messages and docs/release.md (the
RC and patch flows, the workflow list, and a new repo-side tag-ruleset
enforcement note) to point at the workflow instead of a manual push.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Signed-off-by: Myasnikov Daniil <[email protected]>
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request centralizes the creation of pre-release tags into a dedicated GitHub Actions workflow. By moving this responsibility to a CI-driven process, the repository can now enforce strict tag-creation rules, ensuring that all pre-release and stable tags are generated exclusively by the CI app. This change improves security and consistency in the release process while providing a clear path for enabling repository-wide tag protection.

Highlights

  • New Pre-release Workflow: Introduced cut-prerelease.yaml as the centralized, CI-driven entry point for creating pre-release tags (alpha, beta, rc), replacing manual git pushes.
  • Hardened Tag Enforcement: Enables the transition from manual tag-creation discipline to hard enforcement via GitHub tag rulesets, allowing only the CI app to create pre-release and stable tags.
  • Documentation Updates: Updated docs/release.md to reflect the new workflow, clarify release types, and provide guidance on the required rollout order for tag-protection rulesets.
New Features

🧠 You can now enable Memory (public preview) to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Ignored Files
  • Ignored by pattern: .github/workflows/** (2)
    • .github/workflows/cut-prerelease.yaml
    • .github/workflows/tags.yaml
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment Gemini (@gemini-code-assist) Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on Gemini (@gemini-code-assist) comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩

@github-actions github-actions Bot added area/release Issues or PRs related to release tooling (changelog, backport, release pipeline) kind/feature Categorizes issue or PR as related to a new feature labels Jul 9, 2026
@dosubot dosubot Bot added the area/ci Issues or PRs related to CI workflows, GitHub Actions, automation label Jul 9, 2026
@coderabbitai

coderabbitai Bot commented Jul 9, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d2cc33d7-d218-41a2-847a-6b2912cef5e6

📥 Commits

Reviewing files that changed from the base of the PR and between 31cda39 and 4a2e439.

📒 Files selected for processing (2)
  • .github/workflows/cut-prerelease.yaml
  • .github/workflows/tags.yaml
✅ Files skipped from review due to trivial changes (1)
  • .github/workflows/tags.yaml
🚧 Files skipped from review as they are similar to previous changes (1)
  • .github/workflows/cut-prerelease.yaml

📝 Walkthrough

Walkthrough

This PR adds a manual workflow for cutting pre-release tags, updates release documentation and workflow messages to reference it, and pins several workflow actions to fixed commit SHAs.

Changes

Pre-release Tag Cutting

Layer / File(s) Summary
Workflow definition and dispatch
.github/workflows/cut-prerelease.yaml
Declares the manual tag input, concurrency control, and default token permissions.
Tag validation and branch checks
.github/workflows/cut-prerelease.yaml
Validates the tag format, enforces branch/tag alignment, mints a GitHub App token, checks write access, and performs a minimal checkout.
Write-once tag push
.github/workflows/cut-prerelease.yaml
Checks for existing tags, compares branch tips, and pushes the tag commit while logging the pushed SHA.
Workflow messages and release docs
.github/workflows/tags.yaml, docs/release.md
Updates error messages and release guidance to route pre-release creation through the new workflow and reflect the updated release/tag rules.

Workflow Action Pinning

Layer / File(s) Summary
Checkout action pins
.github/workflows/nightly.yaml, .github/workflows/promote-rc.yaml
Checkout steps now reference fixed commit SHAs instead of floating version tags.
GitHub App token pin
.github/workflows/retention.yaml
The GitHub App token action is pinned to a fixed commit SHA.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers: kvaps, lllamnyp, androndo, IvanHunters, sircthulhu, lexfrei

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding the Cut Pre-release Tag workflow for release pre-tags.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/cut-rc-workflow

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request updates the release documentation in docs/release.md to reflect the transition from manual tag pushing to using the cut-prerelease.yaml workflow for cutting pre-releases. It details the updated five-workflow CI release process and outlines repo-side enforcement using GitHub tag-protection rulesets to guarantee tag immutability and creation control. I have no feedback to provide on these changes.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/tags.yaml:
- Around line 104-108: The stable-tag rejection message points users to the
wrong workflow path. Update the error text in the hand-pushed stable tag check
to reference the actual stable-tag flow that finalizes `vX.Y.Z` at the merge
commit, not `promote-rc.yaml`; keep the guidance about cutting a pre-release and
promoting it, but make the stable-tag origin explicit using the
`steps.check_release` / `steps.tag` branch in the `Reject hand-pushed stable
tags` step.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 43bedaa6-e8af-4ef0-a3e9-2b9426e9dd4d

📥 Commits

Reviewing files that changed from the base of the PR and between f91841f and 47eec01.

📒 Files selected for processing (3)
  • .github/workflows/cut-prerelease.yaml
  • .github/workflows/tags.yaml
  • docs/release.md

Comment thread .github/workflows/tags.yaml
The zizmor "Audit workflows" gate (added in #3223) is red on main: five
actions/* references in the release workflows from #3017 are pinned to a
floating tag instead of a commit SHA (blanket unpinned-uses policy, High
severity):

- nightly.yaml (x3)  actions/checkout@v4
- promote-rc.yaml     actions/checkout@v4
- retention.yaml      actions/create-github-app-token@v1

Pin each to the SHA the rest of the repo already standardizes on:
- actions/checkout                -> 34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- actions/create-github-app-token -> d72941d797fd3113feb6b93fd0dec494b13a2547 # v1

`zizmor --offline .github/workflows/` now reports no findings.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Signed-off-by: Myasnikov Daniil <[email protected]>
@IvanHunters

Copy link
Copy Markdown
Collaborator

Verdict

LGTM with non-blocking notes

Clean, defensively-written CI/docs PR. It adds cut-prerelease.yaml (a workflow_dispatch that becomes the single entry point for creating pre-release tags, pushed under the CI App identity), migrates the docs off the manual git push origin HEAD:refs/tags/<tag> recipe, and completes SHA-pinning on the touched workflows. No CRITICAL or MAJOR issues. This review is the consolidation of two independent passes (Opus + Fable); findings 1–3 are worth addressing but none blocks merge.

What was verified (checked, not assumed)

  • Supply-chain pins are authentic. Every newly-pinned SHA resolves to a real upstream release and the # vN comment is accurate: actions/checkout@34e11487… → v4 / v4.3.1; actions/create-github-app-token@d72941d7… → v1 / v1.12.0; actions/github-script@f28e40c7… → v7 / v7.1.0. All match the pins already in tags.yaml, so the PR makes the touched workflows internally consistent. Repo-wide there are now zero @vN refs for checkout / create-github-app-token.
  • GITHUB_TOKEN recursion claim is correct. Pushes made with the default GITHUB_TOKEN do not trigger push-event workflows; an App-token push does. Checkout passes the app token and persists it as origin's credential (persist-credentials defaults on), so the later bare git push origin "HEAD:refs/tags/$TAG" is app-authenticated and tags.yaml fires. Already the mechanism used by promote-rc.yaml / retention.yaml.
  • Write-once is enforced by git itself. The pre-checks are TOCTOU-racy in principle, but the final push is non-forced and git rejects a non-forced push to an existing tag ref; the global concurrency: cut-prerelease (cancel-in-progress: false) additionally serializes dispatches. Three layers.
  • Least privilege is real. Workflow-level permissions: contents: read is correct (the push uses the app token, not GITHUB_TOKEN), and the app token is scoped repositories: ${{ github.event.repository.name }} — tighter than the installation-wide token tags.yaml mints.
  • No injection. inputs.tag reaches shell/JS only via env: + process.env; the regex is anchored; branch names can't contain newlines, so the ::error:: interpolations are safe.
  • Docs ruleset claims hold against the code. Checked nightly.yaml, retention.yaml, promote-rc.yaml, pull-requests-release.yaml: nothing moves or deletes a v* git tag (retention prunes GHCR OCI tags only; promote-rc force-pushes only staging branches), and finalize pushes the stable + api/apps/v1alpha1 tags via the app-token remote — so the proposed "Restrict creations, CI app sole bypass" ruleset would not break the promote flow. No leftover "push by hand" recipe remains, and the workflow count / cross-references in docs/release.md are consistent ("five workflows", all referenced files exist).

Findings

1. [MINOR] main dispatch accepts any version line

cut-prerelease.yaml:113-126 — the line-consistency check constrains only release-* dispatches. From main, any tag is accepted, and downstream tags.yaml Get base branch also accepts main for any tag. So v1.2.5-rc.1 (a patch-line rc) or an old-line v1.4.0-rc.9 can be cut from main, producing a patch-rc built from main content and a wrong-tree staging branch. This contradicts the file's own header comment ("main for a new minor's pre-release (vX.Y.0-rc.N)") and the PR body's "matching release-X.Y branch". Cheap fix: for a main dispatch, require the patch digit Z == 0 (parse already captures it).

2. [MINOR] Stale-tip race can burn a write-once tag name, with a misdiagnosing error

cut-prerelease.yaml:91-98,148-153 + tags.yaml:119 — for workflow_dispatch, checkout (no explicit ref:) pins HEAD to the dispatch-time SHA, not the branch tip at push time. If the branch advances between dispatch and the push step (the window includes any wait in the cut-prerelease concurrency queue), the tag is pushed at a non-tip commit, base_ref comes up empty, and tags.yaml fails — fail-closed, but (a) the write-once tag name is already consumed (maintainer must bump N), and (b) tags.yaml:119's new message ("Cut pre-release tags with the 'Cut Pre-release Tag' workflow … which pushes the tag correctly") misdiagnoses this case, since the workflow was used. Mitigation: right before the push, compare git rev-parse HEAD against git ls-remote origin "refs/heads/$GITHUB_REF_NAME" and fail with "branch advanced since dispatch, re-run" if they differ.

3. [MINOR] PR-body rationale is factually wrong (the change is still justified)

The "Why" section claims "fnmatch has no way to match v* except v*-rc.*", i.e. that a ruleset cannot restrict stable-tag creation while allowing hand-pushed pre-releases. GitHub repository rulesets support separate include and exclude ref-name patterns (verified against GitHub docs), so a "Restrict creations" ruleset with include: refs/tags/v* + exclude: refs/tags/v*-* expresses exactly "stable tags only" today, without this workflow. The workflow's real, independent benefits (audit trail, single entry point, guaranteed base_ref, app-identity push so tags.yaml fires, and enabling creation control over all of v*) stand on their own — but the stated impossibility isn't the actual reason. Worth correcting in the description so future readers don't inherit a false constraint. (Code and committed docs are unaffected — this is PR-description-only.)

4. [NIT] git ls-remote failure is indistinguishable from "tag absent"

cut-prerelease.yaml:138 — git ls-remote --exit-code … >/dev/null 2>&1 treats a transport/auth failure (exit 128) the same as "tag not found" (exit 2). Harmless (the non-forced push is the real enforcement), but checking specifically for exit 2 would avoid a misleading pass.

5. [NIT] Full-history clone for a single ref push

cut-prerelease.yaml:94-95 — fetch-depth: 0 + fetch-tags: true clones the whole monorepo history just to push one ref. fetch-depth: 1 suffices; the local tag pre-check at line 134 is redundant with the live ls-remote on line 138 anyway.

6. [NIT] Validation runs after token mint + checkout

Branch/ref-type validation and the existing-tag check run only after the app token is minted and a full checkout completes; a wrong-branch or already-taken-tag dispatch still mints a token and clones before failing. Reordering the pure-input validation first would fail faster. Cosmetic (the token auto-revokes in the action's post step).

Phase 5b (existing-customer upgrade + fresh install)

N/A. The diff touches only .github/workflows/* and docs/release.md — no packages/, charts, ApplicationDefinition, RBAC, or migrations, so there is no tenant upgrade or fresh-install surface. The only operational dependency is the documented rollout ordering (enable the Restrict creations ruleset only after this workflow ships), which the PR calls out explicitly and correctly.

Summary

Well-scoped and correctly reasoned; the core mechanism follows the repo's pre-existing, documented convention, and the PR honestly flags that the live runtime path is unproven until first real dispatch. Recommend merging after (optionally) tightening the main-dispatch version check and correcting the PR-body rationale.

Non-blocking review notes from #3253:

- Require write access to dispatch. workflow_dispatch already needs write
  access to trigger, but add an explicit getCollaboratorPermissionLevel gate
  (admin/maintain/write) as auditable defense-in-depth that fails closed.
- Constrain a `main` dispatch to a new minor's pre-release (patch Z == 0). A
  patch-line pre-release built from main content would produce a wrong-tree
  release-X.Y.Z-rc.N staging branch; it must be cut from its release-X.Y line.
- Guard against a stale checkout tip. workflow_dispatch pins the dispatch-time
  SHA, not the push-time branch tip (the gap includes the concurrency-queue
  wait); if the branch advanced, the push would tag a stale commit and yield an
  empty base_ref, failing tags.yaml after burning the write-once name. Compare
  local HEAD to the remote branch tip and fail with a re-run hint instead.
- Distinguish ls-remote "tag absent" (exit 2) from a transport/auth failure
  (any other non-zero), so an infra blip cannot read as "free to create".
- Shallow checkout (fetch-depth: 1, no fetch-tags) instead of a full-history
  clone to push one ref; drop the redundant local tag pre-check.
- Run pure-input validation (tag + dispatch branch) before minting the token
  or checking out, so a bad dispatch fails in seconds.

actionlint + zizmor clean; run blocks bash -n checked.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Signed-off-by: Myasnikov Daniil <[email protected]>
@myasnikovdaniil

Copy link
Copy Markdown
Contributor Author

Thanks IvanHunters — thorough review, and the finding-3 catch is a good one. Addressed all six plus a write-access gate (pushed as fix(ci): address review findings):

  1. [MINOR] main accepts any version line — fixed. parse now also emits the patch digit; a main dispatch is rejected unless Z == 0 (a new minor's vX.Y.0-*). Patch-line pre-releases must come from their release-X.Y branch.
  2. [MINOR] Stale-tip race — fixed. Right before the push, the step compares local HEAD against git ls-remote origin refs/heads/<branch> and fails with "branch advanced since dispatch, re-run" if they differ — so it no longer tags a stale commit or burns the write-once name on an empty-base_ref failure.
  3. [MINOR] PR-body rationale wrong — you're right, rulesets express stable-only creation via include + exclude patterns. Corrected the PR description; the impossibility claim is gone and the "Why" now states the workflow's actual value (locking pre-release creation to CI, which the exclude-pattern approach can't). Committed docs were unaffected, as you noted.
  4. [NIT] ls-remote conflates exit 128 and 2 — fixed. It now treats only exit 2 as "absent"; any other non-zero fails as a transport/query error.
  5. [NIT] Full-history clone — fixed. fetch-depth: 1, dropped fetch-tags, and removed the redundant local tag pre-check (the live ls-remote is the real enforcement).
  6. [NIT] Validation after token mint + checkout — fixed. Tag parse + dispatch-branch validation now run first; the token is minted and checkout happens only after.

Separately added a write-access gate: a getCollaboratorPermissionLevel check requires the dispatcher to have admin/maintain/write and fails closed otherwise. workflow_dispatch already requires write to trigger, so this is auditable defense-in-depth.

The runtime path (app-token push, base_ref, and now the permission gate) is still only provable on the first real dispatch — flagged in the PR body.

IvanHunters
IvanHunters previously approved these changes Jul 9, 2026

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

Re-review — all six findings addressed

Verified the fix(ci): address review findings commit against the working tree. Every point from the previous review is resolved, and I re-checked each fix rather than taking it on faith.

# Finding Fix Verified
1 main accepted any version line parse now emits the patch digit; a main dispatch is rejected unless Z == 0 ✅
2 Stale-tip race burned the write-once name Before push, compares local HEAD to git ls-remote origin refs/heads/<branch> and fails "advanced since dispatch, re-run" ✅ fail-closed
3 PR-body rationale wrong Header comment + PR description corrected (rulesets express stable-only via include+exclude; the workflow's real value is CI-locking pre-release creation) ✅
4 ls-remote conflated exit 128 and 2 rc handling: 0 = exists, 2 = absent, any other = transport error → refuse ✅
5 Full-history clone fetch-depth: 1, dropped fetch-tags, removed the redundant local tag pre-check ✅
6 Validation after token mint Tag parse + dispatch-branch validation now run first; token + checkout only after ✅

Both new run: blocks pass bash -n; tags.yaml and docs/release.md from the original commit are untouched.

On the new write-access gate

Sound defense-in-depth, fail-closed. Two non-blocking notes:

  • [NIT] allowed = ['admin', 'maintain', 'write'] — the 'maintain' entry is inert. Per the GitHub API, getCollaboratorPermissionLevel's top-level permission field returns only the legacy base roles admin / write / read / none (maintain → write, triage → read). Behavior is still correct (maintainers pass as write, triage/read/none are denied); the string just never matches and reads as if it distinguishes a role the field can't return.
  • [Runtime, already disclosed] The gate depends on the app-token installation being able to read collaborator permission. If it can't, the API call throws and the step fails closed — for every dispatcher, not just unauthorized ones — until the permission is granted. That's the safe failure direction, and the PR already flags the runtime path as unproven until the first real dispatch; worth confirming on that first run.
  • [NIT] The stale-tip guard's git ls-remote … | cut -f1 masks ls-remote's exit code (no set -o pipefail), so a transport blip surfaces as "Could not resolve the tip" rather than a distinct transport error — slightly inconsistent with the deliberate exit-code distinction added just above for the write-once check. Fail-closed either way.

Verdict

LGTM. All prior findings are correctly resolved with no new defects; what remains is one inert list entry and one disclosed runtime dependency of the new gate, none blocking. The core mechanism still follows the repo's documented convention, and the honest "unproven until first dispatch" caveat stands. Nice, tight follow-up.

Cosmetic follow-ups from the re-review (all fail-closed, no behavior change):

- Write-access gate: drop the inert 'maintain' from the allowed list.
  getCollaboratorPermissionLevel's `permission` field returns only the legacy
  roles admin/write/read/none (maintain collapses to write), so admin+write is
  the full "can push" set; 'maintain' never matched.
- Stale-tip guard: resolve the remote tip without a pipe masking ls-remote's
  exit code, mirroring the write-once query — a transport failure now reports a
  distinct error instead of "branch not found".
- tags.yaml: the hand-pushed-stable rejection message now says stable tags are
  finalized by the promote flow at the merge commit (pull-requests-release.yaml),
  not "come from promote-rc.yaml" — promote-rc opens the PR; finalize cuts the tag.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Signed-off-by: Myasnikov Daniil <[email protected]>
@myasnikovdaniil

Copy link
Copy Markdown
Contributor Author

IvanHunters thanks again for the re-review. Addressed the two remaining nits:

  • 'maintain' inert entry — dropped; allowed is now ['admin', 'write'] (the legacy permission field never returns maintain, as you noted).
  • stale-tip guard exit-code masking — the remote-tip resolution no longer pipes through cut with the exit code masked; a transport failure now reports distinctly, consistent with the write-once check above it.

The push dismissed your approval — mind re-approving when you get a chance? Only those two nits changed since your LGTM. CI is re-running.

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

Re-approve — polish commit verified

Checked 4a2e4396 fix(ci): polish cut-prerelease per re-review nits against the tree. Both remaining nits are resolved correctly, and there's a welcome bonus:

  • 'maintain' inert entry — dropped; allowed is now ['admin', 'write'], with a comment stating why (the legacy permission field collapses maintain→write, triage→read). Matches the GitHub API behavior exactly.
  • stale-tip guard exit-code masking — the remote-tip resolution now captures git ls-remote's exit code (set +e / rc=$?) and fails distinctly on rc != 0 (transport/auth) before the cut + empty-output ("no such branch") check. Consistent with the write-once query above it; a network blip can no longer read as "branch not found".
  • Bonus: tags.yaml's "Reject hand-pushed stable tags" message now reads "Stable tags are finalized by the promote flow at the merge commit" instead of "come from promote-rc.yaml" — the pre-existing wording nit from the original review.

Both run: blocks pass bash -n; diff scope is unchanged (only the expected workflow + docs files). No regressions, no new defects — the changes are strictly a subset of improvements over the state I already approved.

LGTM. Nothing left open across all three review passes. Ship it.

@myasnikovdaniil
myasnikovdaniil enabled auto-merge July 9, 2026 13:26
@myasnikovdaniil
myasnikovdaniil merged commit 6b1e170 into main Jul 9, 2026
17 checks passed
@myasnikovdaniil
myasnikovdaniil deleted the feat/cut-rc-workflow branch July 9, 2026 13:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/ci Issues or PRs related to CI workflows, GitHub Actions, automation area/release Issues or PRs related to release tooling (changelog, backport, release pipeline) kind/feature Categorizes issue or PR as related to a new feature size/L This PR changes 100-499 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants