fix(release): make npm publish resumable - #567
Conversation
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Advanced Run ID: 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 |
There was a problem hiding this comment.
Devin Review found 3 potential issues.
2 flags not posted on this PR by your GitHub settings — view them in Devin Review. (Configure)
| resume_publish: | ||
| description: 'Resume an interrupted release by reusing exact package versions already published to npm' | ||
| required: true | ||
| default: false | ||
| type: boolean |
There was a problem hiding this comment.
🔴 Default recovery publishes another version
With resume_publish=true and default version=patch, release_version advances beyond the interrupted version. The workflow publishes another release because no package matches that version.
Learn more
The recovery flag changes only collision handling. The version resolver still applies the selected bump after healing local versions to npm's highest stable version. After a partial 4.1.0 release from a 4.0.0 checkout, healing selects 4.1.0 and the default patch bump resolves 4.1.1. Every lookup for 4.1.1 is empty, so recovery publishes all packages under an unintended new version.
Example: A run publishes five packages at 4.1.0 and then fails. Rerunning with only resume_publish=true resolves 4.1.1 and publishes eleven new packages instead of finishing the remaining six at 4.1.0.
Recommended fix: Make recovery resolve the interrupted exact version independently of normal bumping. Either require and validate custom_version whenever resume_publish is enabled, or derive a single recoverable version from npm and reject ambiguous or absent candidates.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Fixed in 47b4f7a: recovery now fails early unless an explicit custom_version is supplied, so the default patch choice cannot advance an interrupted release.
| if [ "$RESUME_PUBLISH" != "true" ]; then | ||
| echo "::error title=Version already published::$NPM_NAME@$ver is already on npm. Pick a different bump type, set custom_version to a higher version, or explicitly resume the interrupted release." | ||
| exit 1 | ||
| fi | ||
| echo "$NPM_NAME@$ver: already published — recovery will reuse it" |
There was a problem hiding this comment.
🔴 Current-version recovery skips finalization
With resume_publish=true and version=none, npm recovery completes while Tag + push remains disabled. The interrupted release still lacks its commit and tags.
Learn more
version=none is the existing current-version recovery path. The resolver heals the old checkout to the partially published npm version and keeps that exact version. The new collision handling then reuses published packages and publishes missing ones. However, the commit, lockfile refresh, lockfile verification, tag push, and GitHub release all require version != 'none' or a custom version, so a successful npm recovery never finalizes the release.
Example: Six 4.1.0 packages exist and five are missing. A version=none, resume_publish=true run publishes the five missing packages, reports success, but leaves main at the pre-release manifests and creates no 4.1.0 tags.
Recommended fix: Include resume_publish in every finalization gate and ensure recovery creates or amends the release commit before tagging. Keep the exact-version validation separate from the normal bump condition so all recovery steps use one consistent predicate.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Fixed in 47b4f7a: recovery requires custom_version, which keeps the existing commit/lockfile/tag/release finalization gates enabled. A version=none recovery is rejected before builds.
| EXISTS=$(npm view "$NPM_NAME@$version" version 2>/dev/null || true) | ||
| if [ -n "$EXISTS" ]; then | ||
| if [ "$RESUME_PUBLISH" != "true" ]; then | ||
| echo "::error title=Version already published::$NPM_NAME@$version appeared after preflight; aborting normal release." | ||
| exit 1 | ||
| fi | ||
| echo "==> Reusing $NPM_NAME@$version (already published)" | ||
| continue |
There was a problem hiding this comment.
🔴 Advanced branch mixes release artifacts
When resume_publish runs from a newer commit, existing packages are reused while missing packages come from the newer checkout. One version then contains different source revisions, but its tags identify only the newer revision.
Learn more
Recovery identifies an artifact only by npm name and version. It does not verify the source revision or tarball contents. GitHub dispatch checks out the selected ref at the new run's current revision, so normal merges between attempts change the code, generated changelogs, and rebuilt native artifacts. Existing packages remain from the first run while missing packages are published from the second run, and the later release tag points to only the second checkout.
Example: A run at commit aaa111 publishes four platform packages at 4.1.0 and fails. After main advances to bbb222, recovery reuses those four packages and publishes the other seven from bbb222, then tags bbb222 as the source of all eleven.
Recommended fix: Record the original release SHA and require recovery to check out that exact SHA. Also validate published package provenance or gitHead against it before reuse; abort when an existing package cannot be tied to the recovery source.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Fixed in 47b4f7a: recovery requires the original source SHA, verifies it is an ancestor, rejects all source drift outside this publish-workflow repair, bounds changelog generation to that source, and compares each reused package's unpacked files plus executable modes with the freshly packed artifact before reuse.
87b0ba2 to
47b4f7a
Compare
| exit 1 | ||
| fi | ||
| echo "==> Reusing $NPM_NAME@$version (artifact contents verified)" | ||
| continue |
There was a problem hiding this comment.
Resume comparison rejects rebuilt binaries
High Severity
Recovery rebuilds the eight native platform packages, then reuses an npm version only when the new tarball matches the published one byte-for-byte. Those Rust artifacts link bundled SQLite and are not reproducible across CI runs, so a valid resume fails the compare and never reaches lockfile refresh, tags, or push.
Reviewed by Cursor Bugbot for commit 47b4f7a. Configure here.
There was a problem hiding this comment.
Fixed in 8835752: recovery now requires the original failed Actions run ID, verifies that run used the pinned source SHA, and downloads the CLI/SDK artifacts from that original run instead of using rebuilt binaries. I also verified all 8 retained native artifacts byte-match their published 4.1.0 package payloads.
47b4f7a to
8835752
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
There are 2 total unresolved issues (including 1 from previous review).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 8835752. Configure here.
| exit 1 | ||
| fi | ||
| echo "==> Reusing $NPM_NAME@$version (artifact contents verified)" | ||
| continue |
There was a problem hiding this comment.
Recovery compare fails on workflow fixes
High Severity
Recovery packs from the dispatched HEAD and then requires that tarball to match the already-published artifact. npm pack writes the current commit into the packed package.json as gitHead. A publish-workflow-only repair is a different SHA from resume_source_sha, so reuse fails for every previously published package and the resume path cannot finish.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 8835752. Configure here.
There was a problem hiding this comment.
Audited as a false positive: both npm pack and pnpm pack were executed from this Git checkout and their packed package.json files contain no gitHead field. npm stores gitHead as registry metadata; it is not injected into these tarballs. I also compared a freshly packed relayburn artifact from the newer workflow-only commit against the published package and the unpacked contents matched. The recovery comparison therefore does not fail on the workflow commit SHA.


Summary
resume_publishrecovery mode for interrupted releasesRoot cause
The 4.1.0 publish successfully submitted every package, but npm still reported one platform package as processing when the workflow immediately regenerated
pnpm-lock.yaml. pnpm silently omitted the unavailable optional dependencies, and the subsequent frozen-lockfile check correctly rejected that release commit.Validation
actionlint -ignore 'SC2086:' .github/workflows/publish.ymlgit diff --checkpnpm install --lockfile-only --forcefollowed bypnpm install --frozen-lockfileNote
Medium Risk
Changes release automation and npm publish behavior; mistakes could skip publishes or accept wrong artifacts, but recovery is gated behind explicit inputs and content verification.
Overview
Adds an explicit
resume_publishrecovery path to the publish workflow so a partially shipped release can finish without bumping versions or republishing mismatched tarballs.When recovery is enabled, the workflow validates
custom_version, the original commit SHA, and the failed Actions run ID (including that onlypublish.ymlmay have changed since that commit), re-downloads CLI/SDK artifacts from that run, and uses the original run’s date for changelog stamps. Preflight and publish steps allow npm versions that already exist only in recovery mode; at publish time they diff unpacked package contents and file modes against the registry before skipping upload.Also adds
actions: read, full-history checkout for recovery checks, and a post-publish wait loop that polls until all 11 package versions are visible on npm before regeneratingpnpm-lock.yaml—addressing the case where pnpm silently drops optional deps still “processing” on the registry.Reviewed by Cursor Bugbot for commit 8835752. Bugbot is set up for automated code reviews on this repo. Configure here.