feat(release): version-bump automation + shared release-readiness gate - #139
Merged
Mikola Lysenko (mikolalysenko) merged 1 commit intoJul 31, 2026
Merged
Conversation
scripts/bump-version.sh <X.Y.Z> [--pr] turns the whole version-bump chore
into one command: validates the version (shape, no downgrade, clean tree),
rolls CHANGELOG.md's [Unreleased] notes into a dated `## [X.Y.Z]` section
(refusing when no notes were written), stamps every packaging site via
version-sync.sh, and with --pr opens the release/vX.Y.Z PR whose body
carries the rolled-over notes and the post-merge playbook.
.github/workflows/version-bump.yml exposes the same flow as a
workflow_dispatch so a bump can be started from the GitHub UI. Documented
caveat: PRs opened with GITHUB_TOKEN don't trigger pull_request CI (the
same event suppression that shaped release.yml's topology) — close/reopen
the PR or push to its branch to kick checks; running the script locally is
the preferred path.
scripts/release-lint.sh is the single source of truth for the release
gate, shared by CI and the Release workflow so the two can't drift:
- version coherence: version-sync.sh must be a no-op (catches a
hand-edited version in any single packaging site);
- CHANGELOG has a non-empty section for the version;
- --tag-check: the tag doesn't exist at a commit other than HEAD
(same-commit = release retry, allowed).
ci.yml gains a release-readiness job: coherence-only on ordinary
PRs/pushes, the full gate (incl. tag check) on PRs that change the
workspace version. release.yml's version job now calls the same script in
place of its inline tag/CHANGELOG checks. The full playbook lives in
docs/releasing.md.
Note: awk -v escape-processes its values, so the section matchers use
plain-string prefix matching instead of -v regexes — a -v pattern like
"\[3\.4\.0\]" silently mangles and misreads a populated section as empty
(caught by the scratch-clone round-trip test).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 3 potential issues.
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Post-release lockfile false drift
- Excluded npm/socket-patch/package-lock.json from the coherence check since lockfile changes from optional dependency resolution don't indicate version drift.
Or push these changes by commenting:
@cursor push 3eaf9d1a98
Preview (3eaf9d1a98)
diff --git a/scripts/release-lint.sh b/scripts/release-lint.sh
--- a/scripts/release-lint.sh
+++ b/scripts/release-lint.sh
@@ -79,13 +79,24 @@
else
bash scripts/version-sync.sh "$VERSION" >/dev/null
DRIFTED="$(git status --porcelain | awk '{print $2}')"
- if [ -n "$DRIFTED" ]; then
- fail "version-sync.sh $VERSION is not a no-op — these files carried a stale version: $(echo "$DRIFTED" | tr '\n' ' ')"
+ # Exclude npm/socket-patch/package-lock.json: version-sync refreshes it via
+ # npm install --package-lock-only, which re-resolves optional platform packages
+ # from the registry. After a release publishes those packages, the lockfile
+ # changes from hollow stubs to fully resolved entries (or vice versa when
+ # running on the bump commit before publish), but that's not version drift.
+ DRIFTED_VERSIONS="$(echo "$DRIFTED" | grep -v '^npm/socket-patch/package-lock\.json$' || true)"
+ if [ -n "$DRIFTED_VERSIONS" ]; then
+ fail "version-sync.sh $VERSION is not a no-op — these files carried a stale version: $(echo "$DRIFTED_VERSIONS" | tr '\n' ' ')"
# The tree was clean before the sync, so restoring exactly the files the
# sync touched leaves it as found.
echo "$DRIFTED" | xargs git checkout --
else
- note "version coherence OK: every stamped site already carries $VERSION"
+ if [ -n "$DRIFTED" ]; then
+ note "version coherence OK: every stamped site already carries $VERSION (npm lockfile refreshed from registry, as expected)"
+ echo "$DRIFTED" | xargs git checkout --
+ else
+ note "version coherence OK: every stamped site already carries $VERSION"
+ fi
fi
fiYou can send follow-ups to the cloud agent here.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 365924c. Configure here.
Mikola Lysenko (mikolalysenko)
added a commit
that referenced
this pull request
Jul 30, 2026
…ace) Between a sibling thread's fork() and its exec(), the child briefly inherits every open fd — including a write fd on the binary staged moments ago — and exec'ing the file during that window fails with "Text file busy". Retry the spawn on ErrorKind::ExecutableFileBusy (10 attempts, 25 ms linear backoff, <=1.4 s worst case) instead of failing a fully SHA-verified download; all other spawn errors still fail immediately and the 10 s hang timeout applies per attempt. Same dance Go's os/exec and cargo do. Turns the previous commit's RED regression test green and deflakes the coverage job (first bitten on PR #139: llvm-cov widens the race window, which is why `test`/`test-release` never caught it). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Wenxin Jiang (Wenxin-Jiang)
approved these changes
Jul 31, 2026
Mikola Lysenko (mikolalysenko)
enabled auto-merge (squash)
July 31, 2026 16:19
Mikola Lysenko (mikolalysenko)
merged commit Jul 31, 2026
570339d
into
main
107 of 108 checks passed
Mikola Lysenko (mikolalysenko)
added a commit
that referenced
this pull request
Jul 31, 2026
…en self-update against the fork/exec fd race (#140) * test(update): regression test for the ETXTBSY sanity-exec race (RED) Reproduces the coverage-job flake deterministically: a write fd held open on the staged binary while sanity_exec runs — the shape a sibling thread's fork() leaves behind via fd inheritance — makes the exec fail with "Text file busy" (Linux enforces ETXTBSY; the test is linux-gated). Also tightens the existing strictness test to assert each rejection's REASON instead of bare is_err(), which previously let an ETXTBSY spawn failure masquerade as the expected rejection one line before the flake surfaced. This commit is intentionally pushed without the fix so CI demonstrates the failure; the follow-up commit makes it pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(update): retry sanity-exec on ETXTBSY (fork/exec fd-inheritance race) Between a sibling thread's fork() and its exec(), the child briefly inherits every open fd — including a write fd on the binary staged moments ago — and exec'ing the file during that window fails with "Text file busy". Retry the spawn on ErrorKind::ExecutableFileBusy (10 attempts, 25 ms linear backoff, <=1.4 s worst case) instead of failing a fully SHA-verified download; all other spawn errors still fail immediately and the 10 s hang timeout applies per attempt. Same dance Go's os/exec and cargo do. Turns the previous commit's RED regression test green and deflakes the coverage job (first bitten on PR #139: llvm-cov widens the race window, which is why `test`/`test-release` never caught it). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Summary
Turns the version-bump chore into a one-command / one-click flow, and makes the release-readiness checks a shared, CI-enforced gate so a release can't start with unfinished version chores.
scripts/bump-version.sh <X.Y.Z> [--pr]One command does the whole bump: validates the version (plain X.Y.Z, no downgrade, clean tree), rolls
CHANGELOG.md's[Unreleased]notes into a dated## [X.Y.Z]section (refuses if nobody wrote notes), stamps every packaging site viaversion-sync.sh, and with--prpushesrelease/vX.Y.Zand opens the PR — body pre-filled with the rolled-over release notes and the post-merge playbook..github/workflows/version-bump.ymlThe same flow as a
workflow_dispatch(input: the new version), so a bump can be started from the GitHub UI alone. Documented caveat: a PR opened withGITHUB_TOKENdoesn't triggerpull_requestCI (the same event suppression that shaped release.yml's one-workflow topology) — close/reopen the PR or push to its branch to kick checks; running the script locally is the preferred path and avoids this entirely. No PAT/App token introduced, keeping the zero-long-lived-credential posture.scripts/release-lint.sh— one gate, two enforcement pointsrelease-readiness)Releaseworkflowversionjobversion-sync.shis a no-op across all packaging sitesv<X.Y.Z>absent (or at HEAD = release retry)release.yml'sversionjob now calls the script in place of its inline tag/CHANGELOG checks — the PR gate and the publish gate are the same code and cannot drift.docs/releasing.mdThe full operator playbook: bump → merge → dispatch Release (optional dry-run) → approve staged npm (platform packages first) → "Re-run failed jobs" semantics → pointer to the one-time registry setup checklist in #138.
Verified
3.3.0 → 3.4.0rolls the CHANGELOG correctly (empty[Unreleased]kept on top, 382 lines of notes under the new dated heading), full lint then passes end-to-end; a second bump correctly refuses on the now-empty[Unreleased].vX.Y.Zshape, dirty tree, empty notes.--sync-onlypasses (ordinary-PR CI stays green); the full gate correctly fails for 3.3.0, whose CHANGELOG section genuinely doesn't exist — the next bump supersedes that, but it means a hypothetical 3.3.0 re-release dispatch fails the gate exactly as the old inline check would have.-vescape-processes its values, so-v re="\[3\.4\.0\]"-style patterns silently mangle and misread a populated section as empty — the section matchers use plain-string prefix matching instead.shellcheckclean on all three scripts (now also linted in CI); YAML parses; alluses:SHA-pinned.🤖 Generated with Claude Code
Note
Low Risk
Changes are release automation, CI gates, and docs only—no runtime product code paths.
Overview
Adds one-command / one-click version bumps and centralizes release checks so PR CI and the Release workflow cannot drift.
scripts/bump-version.shvalidates the target version, rolls[Unreleased]into a dated## [X.Y.Z]section (refuses empty notes), runsversion-sync.shacross all packaging sites, and with--propensrelease/vX.Y.Zwith notes and post-merge steps in the body.scripts/release-lint.shis the shared gate: version coherence (version-syncmust be a no-op), non-empty CHANGELOG section, and optional remote tag collision check (tag at HEAD allowed for retries). CI’s newrelease-readinessjob runs sync-only on ordinary PRs and the full gate on version-bump PRs;release.ymlreplaces inline tag/CHANGELOG steps with the same script.version-bump.ymldispatches the bump from Actions (with the documentedGITHUB_TOKEN/pull_requestCI caveat).docs/releasing.mddocuments bump → merge → Release → npm 2FA approval and idempotent re-runs. CI also shellchecks the release scripts.Reviewed by Cursor Bugbot for commit 365924c. Configure here.