Conversation
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 reviews are paused for this user.Troubleshooting steps vary by plan Learn more → On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
|
Caution Review failedPull request was closed or merged during review No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughThe release pipeline is refactored to use short-lived ChangesRelease pipeline: pr-v trigger tag system*
README navigation links
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
| 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 |
There was a problem hiding this comment.
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.
| elif printf '%s' "${out}" | grep -qi 'HTTP 404\|Not Found'; then | |
| elif printf '%s' "${out}" | grep -qiE 'HTTP 404|Not Found'; then |
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.Zon dev.Summary by CodeRabbit
Release Notes
New Features
pr-v*trigger tags for initiating releases.Documentation
Greptile Summary
This PR bootstraps an immutable-tag-safe release pipeline on
main. The key change is replacing directv*trigger tags with short-livedpr-v*trigger tags that drive PR creation and are deleted immediately, while the authoritativevX.Y.Ztag is created exactly once — via a plain (non-force) push — on the squash commit at merge time.release-intake.yml: Now fires onpr-v*tags; validates format, derives the version, runs fail-closed immutability probes for the targetv*tag and release, opens the release PR, then deletes the trigger tag.post-merge-release.yml: Replaces the old force-push + SHA-verification sequence withassert_release_absent/assert_release_tag_absentfollowed by a single plaingit push, ensuring thev*tag is only ever created once and cannot overwrite an existing immutable tag.release-policy.sh: AddsPR_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
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%%{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 ReleaseComments Outside Diff (1)
.github/workflows/release-intake.yml, line 112-126 (link)gh pr createis called. Ifgh pr createfails 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-creatingpr-v{TAG}and checking the workflow logs. Moving the deletion to after a successfulgh pr createkeeps 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).Reviews (1): Last reviewed commit: "ci(release): immutable-tag-safe flow (pr..." | Re-trigger Greptile