Skip to content

Release v2.2.1#13

Merged
tis24dev merged 2 commits into
mainfrom
dev
Jun 20, 2026
Merged

Release v2.2.1#13
tis24dev merged 2 commits into
mainfrom
dev

Conversation

@tis24dev

@tis24dev tis24dev commented Jun 20, 2026

Copy link
Copy Markdown
Owner

Bootstrap release: install the immutable-tag-safe release pipeline (pr-v* trigger, create v* tag once) on main as the v2.2.1 release commit.

After this merges, main carries the new flow and future releases use git tag pr-vX.Y.Z on dev.

Summary by CodeRabbit

Release Notes

  • New Features

    • Release intake workflow now accepts pr-v* trigger tags for initiating releases.
    • Enhanced pre-release validation with immutability checks to prevent conflicts with existing tags or releases.
  • Documentation

    • Added quick start navigation link to README.

Greptile Summary

This PR bootstraps an immutable-tag-safe release pipeline on main. The key change is replacing direct v* trigger tags with short-lived pr-v* trigger tags that drive PR creation and are deleted immediately, while the authoritative vX.Y.Z tag is created exactly once — via a plain (non-force) push — on the squash commit at merge time.

  • release-intake.yml: Now fires on pr-v* tags; validates format, derives the version, runs fail-closed immutability probes for the target v* tag and release, opens the release PR, then deletes the trigger tag.
  • post-merge-release.yml: Replaces the old force-push + SHA-verification sequence with assert_release_absent / assert_release_tag_absent followed by a single plain git push, ensuring the v* tag is only ever created once and cannot overwrite an existing immutable tag.
  • release-policy.sh: Adds PR_TAG_REGEX, conversion helpers, and the new fail-closed existence probes (remote_tag_state, remote_release_state, assert_*_absent) shared by both workflows.

Confidence Score: 4/5

Safe to merge — the immutability-gating logic is sound and the fail-closed probes prevent silent mis-classification of network errors as absent.

Both findings are non-blocking: the grep portability issue only affects non-GNU runners (all current runners are Ubuntu), and the trigger-before-PR-create ordering only costs the user a manual re-push on a transient gh pr create failure. The core invariant — the v* tag is created exactly once via a plain push and immutability probes fail closed on error — is correctly implemented.

.github/workflows/release-intake.yml for the trigger-deletion ordering, and .github/scripts/release-policy.sh for the grep portability fix.

Important Files Changed

Filename Overview
.github/scripts/release-policy.sh Adds PR_TAG_REGEX, is_pr_tag, release_tag_from_pr_tag, and fail-closed existence probes. One minor portability concern: grep -qi 'HTTP 404|Not Found' uses a GNU BRE extension instead of -E.
.github/workflows/release-intake.yml Switches trigger from v* to pr-v*, adds deletion-event guard, pre-flight immutability checks, and simplified idempotency. The trigger tag is deleted before gh pr create, which loses the retry handle on transient PR creation failures.
.github/workflows/post-merge-release.yml Replaces force-push + sha-verification with assert_release_absent / assert_release_tag_absent + plain git push, aligning with the immutable-tag model. Logic is clean and correct.
README.md Adds a quick-start navigation bar between two --- separators. Cosmetic-only change.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Dev as Developer
    participant GH as GitHub (dev branch)
    participant RI as release-intake.yml
    participant PMR as post-merge-release.yml
    participant Rel as release.yml (GoReleaser)

    Dev->>GH: git push pr-v2.2.1 (unprotected tag)
    GH-->>RI: "push event (pr-v* trigger)"
    RI->>RI: source policy from origin/main
    RI->>RI: "validate pr-v2.2.1 format, derive TAG=v2.2.1"
    RI->>GH: check tag v2.2.1 absent (fail closed)
    RI->>GH: check release v2.2.1 absent (fail closed)
    RI->>GH: "gh pr create dev->main Release v2.2.1"
    RI->>GH: delete pr-v2.2.1 trigger tag
    Note over Dev,GH: PR reviewed and squash-merged
    GH-->>PMR: pull_request closed (merged)
    PMR->>PMR: "extract release-tag marker = v2.2.1"
    PMR->>GH: assert release v2.2.1 absent
    PMR->>GH: assert tag v2.2.1 absent
    PMR->>GH: "git push MERGE_SHA -> refs/heads/dev"
    PMR->>GH: git tag -a v2.2.1 MERGE_SHA
    PMR->>GH: git push refs/tags/v2.2.1 (plain create, ruleset-safe)
    GH-->>Rel: push v2.2.1 tag triggers release.yml
    Rel->>GH: GoReleaser builds + signs + publishes GitHub Release
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Dev as Developer
    participant GH as GitHub (dev branch)
    participant RI as release-intake.yml
    participant PMR as post-merge-release.yml
    participant Rel as release.yml (GoReleaser)

    Dev->>GH: git push pr-v2.2.1 (unprotected tag)
    GH-->>RI: "push event (pr-v* trigger)"
    RI->>RI: source policy from origin/main
    RI->>RI: "validate pr-v2.2.1 format, derive TAG=v2.2.1"
    RI->>GH: check tag v2.2.1 absent (fail closed)
    RI->>GH: check release v2.2.1 absent (fail closed)
    RI->>GH: "gh pr create dev->main Release v2.2.1"
    RI->>GH: delete pr-v2.2.1 trigger tag
    Note over Dev,GH: PR reviewed and squash-merged
    GH-->>PMR: pull_request closed (merged)
    PMR->>PMR: "extract release-tag marker = v2.2.1"
    PMR->>GH: assert release v2.2.1 absent
    PMR->>GH: assert tag v2.2.1 absent
    PMR->>GH: "git push MERGE_SHA -> refs/heads/dev"
    PMR->>GH: git tag -a v2.2.1 MERGE_SHA
    PMR->>GH: git push refs/tags/v2.2.1 (plain create, ruleset-safe)
    GH-->>Rel: push v2.2.1 tag triggers release.yml
    Rel->>GH: GoReleaser builds + signs + publishes GitHub Release
Loading

Comments Outside Diff (1)

  1. .github/workflows/release-intake.yml, line 112-126 (link)

    P2 The trigger tag is deleted before gh pr create is called. If gh pr create fails for a transient reason (network, rate limit, token scope), the trigger is already gone — the user has no artifact to re-push without manually re-creating pr-v{TAG} and checking the workflow logs. Moving the deletion to after a successful gh pr create keeps the trigger alive as a retry handle without affecting the guard against deletion-re-trigger (that guard fires on the new push event regardless of timing).

    Fix in Claude Code Fix in Cursor Fix in Codex

Fix All in Claude Code Fix All in Cursor Fix All in Codex

Reviews (1): Last reviewed commit: "ci(release): immutable-tag-safe flow (pr..." | Re-trigger Greptile

Greptile also left 1 inline comment on this PR.

tis24dev added 2 commits June 19, 2026 15:22
Reworks the governed dev->main release pipeline so it never updates or deletes a
v* tag, making it compatible with a tag-immutability ruleset WITHOUT any bypass:
- trigger via an UNPROTECTED pr-vX.Y.Z tag (release-intake on push tags pr-v*),
  keeps the trusted-base policy sourcing from origin/main; opens the dev->main PR
  and deletes the trigger tag. No v* tag is created during intake.
- post-merge-release creates the annotated vX.Y.Z tag ONCE on the squash commit
  via a PLAIN (non-force) push (a creation); that push re-triggers release.yml ->
  GoReleaser. Removed git tag -f / force-push / delete fallback / backstop.
- release-policy.sh: add is_pr_tag + release_tag_from_pr_tag and fail-closed
  remote_tag_state / remote_release_state existence probes.
release-guard.yml and release.yml unchanged.

Pattern: addhOn diagnostics/release-pipeline-immutable-tags.md
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@github-actions

Copy link
Copy Markdown

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

Pull request was closed or merged during review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 029c9b33-1d39-4687-8ebf-3fd93c6c879d

📥 Commits

Reviewing files that changed from the base of the PR and between d4913dd and bc1fadb.

📒 Files selected for processing (4)
  • .github/scripts/release-policy.sh
  • .github/workflows/post-merge-release.yml
  • .github/workflows/release-intake.yml
  • README.md

📝 Walkthrough

Walkthrough

The release pipeline is refactored to use short-lived pr-v* trigger tags instead of directly pushing v* release tags. The policy script gains parsing helpers and fail-closed remote existence gates. The release-intake workflow and post-merge release workflow are updated to consume the new trigger tag pattern and assert release/tag absence before acting.

Changes

Release pipeline: pr-v trigger tag system*

Layer / File(s) Summary
Policy script: PR_TAG_REGEX, parsing helpers, and remote state gates
.github/scripts/release-policy.sh
Adds PR_TAG_REGEX, is_pr_tag, release_tag_from_pr_tag, remote_tag_state (via git ls-remote), remote_release_state (via gh api with 404 handling), assert_release_tag_absent, and assert_release_absent, all failing closed on present or error.
release-intake.yml: pr-v* trigger, deletion guard, and rewritten intake logic
.github/workflows/release-intake.yml
Changes the workflow trigger from v* to pr-v*, adds a tag-deletion skip guard, and replaces the intake shell script to source policy from origin/main, validate and consume the trigger tag via is_pr_tag/release_tag_from_pr_tag, enforce origin/dev HEAD alignment, probe remote state with the new gates, handle idempotency against an existing PR's release-tag marker, delete the consumed trigger tag, and open the PR with only a release-tag body marker.
post-merge-release.yml: assert-absent non-force tag publish
.github/workflows/post-merge-release.yml
Replaces the force/repair tag publishing block (release existence check, git tag -fa, delete/recreate fallback, ls-remote SHA backstop) with assert_release_absent + assert_release_tag_absent followed by git tag -a and a non-force push to refs/tags/${TAG}.

README navigation links

Layer / File(s) Summary
README inline navigation row
README.md
Adds a centered inline link row with Quick start and other section anchors after the introductory divider.

Sequence Diagram(s)

sequenceDiagram
  participant Dev as Developer
  participant Remote as GitHub remote
  rect rgba(173, 216, 230, 0.5)
    note over Dev,Remote: Intake phase
    Dev->>Remote: push pr-vX.Y.Z on dev
    Remote->>Remote: trigger release-intake.yml
    Remote->>Remote: source release-policy.sh from origin/main
    Remote->>Remote: is_pr_tag → derive TAG via release_tag_from_pr_tag
    Remote->>Remote: verify trigger commit == origin/dev HEAD
    Remote->>Remote: remote_release_state(TAG) / remote_tag_state(TAG)
    Remote->>Remote: delete trigger tag (consumed or error)
    Remote->>Remote: open PR main ← dev with release-tag marker
  end
  rect rgba(144, 238, 144, 0.5)
    note over Dev,Remote: Post-merge phase
    Remote->>Remote: assert_release_absent(TAG)
    Remote->>Remote: assert_release_tag_absent(TAG)
    Remote->>Remote: git tag -a TAG on MERGE_SHA
    Remote->>Remote: git push origin refs/tags/TAG (non-force)
    Remote->>Remote: release.yml cuts the release
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 A pr-v tag hops in, soft and free,
The rabbit checks gates — closed tight, you see.
No force, no repair, just "absent" must hold,
Trigger consumed, then the PR is enrolled.
The version tag lands on the squash commit's ear,
And release.yml cuts the ribbon from here! 🎀

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Release v2.2.1' is partially related to the changeset—it indicates a version number, but the PR is fundamentally about establishing a new immutable-tag-safe release pipeline, not just releasing v2.2.1. The main changes involve reworking the CI/CD flow, not the release itself. Consider a more specific title that highlights the pipeline redesign, such as 'Implement immutable-tag-safe release pipeline via pr-v* triggers' or 'Bootstrap v2.2.1 with immutable-tag release flow'.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dev

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 and usage tips.

@codecov

codecov Bot commented Jun 20, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@tis24dev tis24dev merged commit f3c92ad into main Jun 20, 2026
15 of 16 checks passed
out="$(gh api "repos/${GITHUB_REPOSITORY}/releases/tags/${tag}" 2>&1)" || rc=$?
if [[ "${rc}" -eq 0 ]]; then
echo "present"
elif printf '%s' "${out}" | grep -qi 'HTTP 404\|Not Found'; then

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 The \| alternation inside grep -q is a GNU BRE extension, not POSIX. On any runner that ships a non-GNU grep (Alpine, macOS self-hosted) the pattern would be treated as a literal \| and the 404-detection branch would never match, silently returning "error" for every absent release and causing all pre-flight gates to abort. Using -E and removing the escaping makes the intent unambiguous.

Suggested change
elif printf '%s' "${out}" | grep -qi 'HTTP 404\|Not Found'; then
elif printf '%s' "${out}" | grep -qiE 'HTTP 404|Not Found'; then

Fix in Claude Code Fix in Cursor Fix in Codex

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.

1 participant