Skip to content

feat(ci): release on a VERSION change instead of on a tag push - #41

Merged
WomB0ComB0 merged 7 commits into
mainfrom
feat/release-on-version-change
Aug 11, 2026
Merged

feat(ci): release on a VERSION change instead of on a tag push#41
WomB0ComB0 merged 7 commits into
mainfrom
feat/release-on-version-change

Conversation

@WomB0ComB0

@WomB0ComB0 WomB0ComB0 commented Aug 10, 2026

Copy link
Copy Markdown
Member

Answers "shouldn't the tag push be automatic?" — yes, and my original reasoning had expired.

Why the old design was wrong

I made tagging the human gate when the tag triggered the deploy. Once Workers Builds started deploying worker/ from main, the real gate became merging a reviewed VERSION/PINS change — already CODEOWNERS-gated. Tagging afterwards added no judgement, only a step that could be forgotten, leaving VERSION bumped, installers stamped, and no release to match.

Why I did not simply automate the tag push

Two blockers, both recorded in the workflow so nobody rediscovers them painfully:

  1. The release-tags ruleset rejects refs/tags/v* creation by anyone outside @resq-software/installer-maintainers — bots included.
  2. A tag pushed with GITHUB_TOKEN starts no workflow run. Same suppression that would have left every pin-bump PR without its required check.

A workflow that pushed a tag would hit (1); if it got past that, it would create a tag that triggered nothing.

What this does instead

Inverts the trigger. The workflow runs because VERSION changed and creates the tag as an output of its own work:

merge a VERSION change to main     <- the reviewed decision
  |- validate VERSION shape
  |- stamp --check                 <- before tagging, so a bad commit gets none
  |- create tag vX.Y.Z via API
  |- compute pins (needs the tag to exist)
  |- verify GitHub serves those bytes + worker tests
  |- publish Release + SHA256SUMS
  `- open the pin-bump PR + dispatch required.yml

Nothing waits on a tag, so (2) cannot apply.

Idempotent throughout, because a partly-failed release has to be re-runnable: an existing tag at the same commit continues; one pointing elsewhere fails loudly rather than republishing different bytes under a version people already pinned; the release refreshes instead of erroring; the bump PR updates in place.

Needs from you, once

TEAM_ID=$(gh api orgs/resq-software/teams/installer-maintainers --jq .id)
jq -n --argjson id "$TEAM_ID" '{name:"release-tags",target:"tag",enforcement:"active",
  bypass_actors:[{actor_id:$id,actor_type:"Team",bypass_mode:"always"},
                 {actor_id:5,actor_type:"RepositoryRole",bypass_mode:"always"}],
  conditions:{ref_name:{include:["refs/tags/v*"],exclude:[]}},
  rules:[{type:"creation"},{type:"update"},{type:"deletion"}]}' \
  | gh api -X PUT repos/resq-software/dev/rulesets/20622472 --input -

That reads like a loosening but is stricter in practice: it moves the privileged act from "create a tag" — which today any maintainer can do on any commit with no review — to "merge a reviewed VERSION change".

I have not run it. Org rules are yours to change.

Verified

YAML valid, 3 jobs, 0 secrets.* references. The VERSION shape guard accepts 0.4.0 and rejects 1..2, 1.2, 1.2.3.4, ., abc — matching bin/stamp.sh. AGENTS.md updated; no manual tag instructions remain anywhere in the docs.

Stacked note: independent of #40 (worker-live window), which touches only worker-live.yml.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Release Process

    • Releases now start when a version change is merged into the main branch.
    • Version tags, release artifacts, checksums, and verification details are created automatically.
    • Re-running a release safely refreshes the existing release without creating duplicates.
    • Automated checks help prevent version or tag mismatches and verify stamped installers.
    • Pin updates are prepared idempotently after release creation.
  • Bug Fixes

    • Improved handling when required pin declarations are missing.
  • Documentation

    • Updated release guidance, verification steps, tag restrictions, and deployment timing.

Tagging was the human gate when the tag triggered the deploy. That stopped
being true once Workers Builds began deploying worker/ from main: the act that
decides what users receive is merging a reviewed VERSION and PINS change, both
CODEOWNERS-gated. Tagging afterwards added no judgement, only a step that could
be forgotten — leaving VERSION bumped, installers stamped, and no release.

Automating the tag push does not work, and the reasons are recorded in the
workflow so nobody rediscovers them the hard way:

  1. the release-tags ruleset rejects tag creation by anyone outside
     installer-maintainers, bots included;
  2. a tag pushed with GITHUB_TOKEN starts no workflow run — the same
     suppression that would have left every pin-bump PR unmergeable.

So the trigger is inverted rather than the push automated. The workflow runs
because VERSION changed and creates the tag as an output of its own work, so
nothing waits on a tag and (2) cannot apply. Only (1) remains, as a one-time
ruleset change.

Tag creation is idempotent: an existing tag at the same commit continues, and
one pointing elsewhere fails loudly rather than republishing different bytes
under a version people already pinned. Release creation and the bump PR are
idempotent too, so re-running a partly-failed release is safe.

Ordering: stamp --check runs before the tag is created, so an unstamped commit
never gets one. Pins are computed after, since gen-pins derives them from tags.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@WomB0ComB0
WomB0ComB0 requested a review from a team as a code owner August 10, 2026 23:33
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 10, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
get-resq-software 275ed42 Aug 11 2026, 04:28 AM

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@WomB0ComB0, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 51 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 202a5825-5167-4912-a92c-0d0a10e3f4af

📥 Commits

Reviewing files that changed from the base of the PR and between 7277997 and 275ed42.

📒 Files selected for processing (1)
  • .github/workflows/required.yml
📝 Walkthrough

Walkthrough

The release process now starts from merged VERSION changes on main. The workflow validates stamping, creates or verifies the tag, publishes rerunnable release artifacts, and manages the pin-bump pull request. Pin generation now reports missing PINS blocks correctly.

Changes

Automated release flow

Layer / File(s) Summary
VERSION release trigger and process contract
.github/workflows/release.yml, AGENTS.md
The workflow triggers on VERSION changes and manual dispatches. Documentation describes tag restrictions, token event suppression, and stamping requirements.
Version verification and tag creation
.github/workflows/release.yml
The verify job validates VERSION and installer stamps, creates or validates the derived tag, and computes pins from tagged history.
Release publishing and pin-bump automation
.github/workflows/release.yml
The release job refreshes existing assets safely. The bump job fetches tags and updates or creates the pin-bump pull request idempotently.
Missing PINS regression validation
bin/gen-pins.sh, .github/workflows/required.yml
gen-pins.sh now reaches its missing-block diagnostic when no PINS declaration exists. The required workflow verifies this behavior in a temporary repository.

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

Sequence Diagram(s)

sequenceDiagram
  participant VERSION change
  participant verify job
  participant Git tag
  participant release job
  participant pin-bump pull request
  participant Workers Builds
  VERSION change->>verify job: trigger workflow
  verify job->>Git tag: create or validate tag
  verify job->>release job: pass tag, version, and pins
  release job->>pin-bump pull request: create or update pin changes
  pin-bump pull request->>Workers Builds: trigger deployment verification after merge
Loading

Possibly related PRs

🚥 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 and concisely describes the primary change: triggering releases from VERSION changes instead of tag pushes.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/release-on-version-change

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.

WomB0ComB0 and others added 2 commits August 10, 2026 20:20
SC2016: a literal $REL inside single quotes, which actionlint fails the build
on. Same class as the repo-drift sed patterns earlier.

Escaping it would read worse than expanding $VERSION directly, and full URLs
are better release notes anyway — the reader can paste them without first
setting a variable. The "verify against one immutable release" property is
unchanged: both URLs carry the same version.

The jq programs elsewhere in this file also contain $ in single quotes and are
not flagged; shellcheck knows jq takes a literal script. Those lines passed
actionlint on the previous four PRs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

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

Actionable comments posted: 2

🤖 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/release.yml:
- Line 97: Redirect all diagnostic echo messages in
.github/workflows/release.yml at lines 97-97, 122-124, 201-203, 290-294, and
323-327 to stderr, covering release, tag, existing-release, branch/PR, and
dispatch status messages. Leave any $GITHUB_OUTPUT writes unchanged.
- Around line 42-47: Restrict the manual-release path in the workflow before tag
creation: permit new releases only when github.ref is refs/heads/main, while
allowing recovery only from an existing matching release tag or an immutable
commit SHA. Dereference annotated tags and compare the resolved target commit
with GITHUB_SHA, and make verify/tag creation fail for any other ref.
🪄 Autofix

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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9a4ffb85-c2d2-4ef5-b161-48f5701b817b

📥 Commits

Reviewing files that changed from the base of the PR and between bb05ab1 and e05750d.

📒 Files selected for processing (2)
  • .github/workflows/release.yml
  • AGENTS.md

Comment thread .github/workflows/release.yml
Comment thread .github/workflows/release.yml
WomB0ComB0 and others added 2 commits August 10, 2026 20:25
PR #40 silently reverted bin/gen-pins.sh. The `|| true` on the grep -c count
was present in 3cf1268, 017ec1b and cc65869, and absent from bb05ab1. My fault:
I used `git add -A` when creating that branch, which staged a stale copy of a
file the PR had no business touching.

Without it, grep -c exits 1 on zero matches, the command substitution inherits
that status, and set -e kills the script one line before the die that explains
the problem — so the guard is unreachable in exactly the case it exists for,
and a missing PINS block becomes a silent nonzero exit.

Restoring it is the small half. The real problem is that a fix with no test
disappeared and nothing noticed for three merges, so required.yml now asserts
the guard is reachable. Verified both directions: with the fix the check
reports "found 0"; with the revert applied it produces no output at all and the
check fails.

The test asserts on the message rather than on a source line, so a future
rewrite still has to preserve the behaviour rather than just the spelling.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@WomB0ComB0

Copy link
Copy Markdown
Member Author

Pushed two things beyond the actionlint fix.

actionlint — SC2016 again: a literal $REL inside single quotes in the release notes. Dropped the variable and emit full URLs instead; no escaping, and the notes are now copy-pasteable without setting anything first.

A regression I caused in #40. Merging main into this branch pulled in a silent revert of bin/gen-pins.sh. The || true on the grep -c count was present in 3cf1268, 017ec1b, cc65869 and absent from bb05ab1. Cause: I used git add -A when creating the #40 branch, which staged a stale copy of a file that PR had no business touching.

Without it the guard is unreachable in exactly the case it exists for — grep -c exits 1 on zero matches, set -e kills the script one line before the die, and a missing PINS block becomes a silent nonzero exit.

Restoring it is the small half. The real problem is that a fix with no test vanished and nothing noticed across three merges. required.yml now asserts the guard is reachable, verified both directions: with the fix it reports found 0; with the revert applied it produces no output at all and the check fails. It asserts on the message, not a source line, so a rewrite has to preserve the behaviour rather than the spelling.

workflow_dispatch lets a caller pick any ref, and this workflow did not check
github.ref before creating a tag with contents: write. Anyone with write access
could have pushed a branch carrying a bumped VERSION and an altered install.sh,
dispatched a release, and obtained a tag plus a published Release with
SHA256SUMS — from code that was never reviewed.

That is precisely the gate the trigger inversion was meant to enforce, so the
hole was in the same change that argued for it.

Now only refs/heads/main, or refs/tags/v$VERSION for recovery. The tag path
cannot publish anything new: the tag step finds it already pointing at that
commit and continues, and tag creation is itself restricted by the release-tags
ruleset, so the ref is already privileged.

Exercised: main and refs/tags/v0.4.0 allow; refs/heads/attacker,
refs/tags/v9.9.9 and refs/pull/99/merge block. The guard runs before tag
creation, not after.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

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

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/required.yml:
- Around line 58-81: Update the “gen-pins missing-PINS guard is reachable”
workflow step to capture and validate gen-pins.sh’s exit status using an if
condition, failing the test if the command succeeds. When it fails, assert the
complete diagnostic emitted at bin/gen-pins.sh line 122 rather than only
matching “found 0”; preserve the existing error reporting for unexpected output.
🪄 Autofix

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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e0996fac-8fd5-49e5-b4c3-120d7c7324dd

📥 Commits

Reviewing files that changed from the base of the PR and between e05750d and 7277997.

📒 Files selected for processing (3)
  • .github/workflows/release.yml
  • .github/workflows/required.yml
  • bin/gen-pins.sh
🚧 Files skipped from review as they are similar to previous changes (1)
  • .github/workflows/release.yml

Comment thread .github/workflows/required.yml
The guard test checked only the message and swallowed the status with
`|| true`. A script that printed the diagnostic and exited 0 would have passed
it — while being broken in exactly the way that matters, since the guard exists
to stop the run rather than to narrate.

Confirmed the gap is real: given a stub that prints the message and exits 0,
the old check passes and the new one fails the build.

Now asserts the status first, then matches the complete diagnostic rather than
the "found 0" fragment, so a message drifting into a different meaning cannot
keep the check green.

Verified: correct code passes; the revert (no `|| true`) fails with no output;
print-then-exit-0 fails on the status.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@WomB0ComB0
WomB0ComB0 merged commit e837c3c into main Aug 11, 2026
22 checks passed
@WomB0ComB0
WomB0ComB0 deleted the feat/release-on-version-change branch August 11, 2026 04:48
WomB0ComB0 added a commit that referenced this pull request Aug 11, 2026
…ag (#42)

The first real release failed:

  v0.4.0 already exists at {"message":"Not Found",...,"status":"404"}
  but this commit is e837c3c

`gh api` writes its error body to stdout on a 404, and only stderr was
redirected. So `existing` was set to a JSON error object rather than the empty
string, the "already exists" branch fired, and the run reported a collision
with a tag that does not exist. Every first release of any version would have
hit this.

Reproduced against the live API before and after: old logic yields the 404 body,
new logic yields empty and proceeds to create.

Also dereference annotated tags, which CodeRabbit raised on #41 and I skipped
on the reasoning that it only affected the recovery path. It affects this check
too: an annotated tag's ref points at a tag object, so comparing that id with
GITHUB_SHA always differs and reads as a collision. Not hypothetical —
resq-software/crates carries annotated tags, and a maintainer tagging by hand
usually produces one.

The failure was safe: no tag created, nothing published, both later jobs
skipped. Whether github-actions[bot] can bypass the release-tags ruleset is
still unknown, because the run never reached the create call.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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