Skip to content

fix(release): make npm publish resumable - #567

Merged
kjgbot merged 1 commit into
mainfrom
codex/make-publish-resumable
Sep 20, 2026
Merged

kjgbot merged 1 commit into
mainfrom
codex/make-publish-resumable

Conversation

@kjgbot

@kjgbot kjgbot commented Sep 20, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add an explicit resume_publish recovery mode for interrupted releases
  • reuse immutable npm versions only when recovery is explicitly enabled
  • wait for all 11 npm packages to become readable before regenerating the pnpm lockfile

Root 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.yml
  • YAML parse
  • git diff --check
  • isolated 4.1.0 reproduction: pnpm install --lockfile-only --force followed by pnpm install --frozen-lockfile

Note

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_publish recovery 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 only publish.yml may 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 regenerating pnpm-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.

@coderabbitai

coderabbitai Bot commented Sep 20, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 85c712d9-3e68-4246-8105-a3b2e7848638


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.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Devin Review found 3 potential issues.

2 flags not posted on this PR by your GitHub settings — view them in Devin Review. (Configure)

Devin Review

Comment on lines +42 to +46
resume_publish:
description: 'Resume an interrupted release by reusing exact package versions already published to npm'
required: true
default: false
type: boolean

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔴 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.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 47b4f7a: recovery now fails early unless an explicit custom_version is supplied, so the default patch choice cannot advance an interrupted release.

Comment on lines +511 to +515
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"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔴 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.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.

Comment thread .github/workflows/publish.yml Outdated
Comment on lines +1133 to +1140
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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔴 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.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.

@kjgbot
kjgbot force-pushed the codex/make-publish-resumable branch from 87b0ba2 to 47b4f7a Compare September 20, 2026 16:12

@cursor cursor 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.

Stale Bugbot comment from a previous run.

exit 1
fi
echo "==> Reusing $NPM_NAME@$version (artifact contents verified)"
continue

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 47b4f7a. Configure here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.

Comment thread .github/workflows/publish.yml
@kjgbot
kjgbot force-pushed the codex/make-publish-resumable branch from 47b4f7a to 8835752 Compare September 20, 2026 16:22

@cursor cursor 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.

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).

Fix All in Cursor

❌ 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 8835752. Configure here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.

@kjgbot
kjgbot merged commit d6595aa into main Sep 20, 2026
5 checks passed
@kjgbot
kjgbot deleted the codex/make-publish-resumable branch September 20, 2026 16:36
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