ci(release): reconcile lockfile before gated changeset commands#1104
Conversation
verifyDepsBeforeRun: error gates pnpm run/exec. The frozen install in the release job does not refresh the lockfile's overrides hash, so a lockfile whose overrides drifted from pnpm-workspace.yaml passes the frozen install but trips the gate on `pnpm run changeset:version` / `pnpm changeset publish`, killing every release before it versions or publishes. A non-frozen install is not gated and reconciles the hash; changesets/action commits the result, self-healing the repo.
|
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-perf-coordinator | 8b49a64 | May 19 2026, 06:49 PM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-i18n | 8b49a64 | May 19 2026, 06:49 PM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-demo-cache | 8b49a64 | May 19 2026, 06:51 PM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
docs | 8b49a64 | May 19 2026, 06:50 PM |
@emdash-cms/admin
@emdash-cms/auth
@emdash-cms/blocks
@emdash-cms/cloudflare
emdash
create-emdash
@emdash-cms/gutenberg-to-portable-text
@emdash-cms/x402
@emdash-cms/plugin-ai-moderation
@emdash-cms/plugin-atproto
@emdash-cms/plugin-audit-log
@emdash-cms/plugin-color
@emdash-cms/plugin-embeds
@emdash-cms/plugin-forms
@emdash-cms/plugin-webhook-notifier
commit: |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-playground | 8b49a64 | May 19 2026, 06:53 PM |
There was a problem hiding this comment.
Pull request overview
Updates the GitHub Actions release workflow to prevent verifyDepsBeforeRun: error from aborting the release job before versioning/publishing when the lockfile’s recorded overrides metadata has drifted from pnpm-workspace.yaml.
Changes:
- Adds a “Reconcile lockfile” step that runs a non-frozen
pnpm installbefore the Changesets step to refresh lockfile metadata so gated pnpm commands can run. - Keeps
verifyDepsBeforeRun: errorunchanged for local dev and normal CI; only the release workflow is adjusted.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # Not redundant with the frozen install: frozen install does not | ||
| # refresh the lockfile's overrides hash, so a drifted lockfile trips | ||
| # verifyDepsBeforeRun on the gated changeset commands below. | ||
| - name: Reconcile lockfile | ||
| run: pnpm install --no-frozen-lockfile |
There was a problem hiding this comment.
Good catch — and digging into it showed the problem is deeper than placement. changesets/action runs git checkout changeset-release/main + git reset --hard <main> immediately before it invokes the version/publish command, inside the action. So a reconcile placed in any earlier workflow step (whether before or after pnpm build) gets its working-tree lockfile discarded by that git reset --hard, and the gated pnpm changeset call still trips verifyDepsBeforeRun. Moving the step earlier wouldn't have fixed it.
I confirmed the committed lockfile overrides actually matched pnpm-workspace.yaml at the failing SHA, yet the version command still failed — the only place a reconcile provably holds is right before the gated pnpm call, after all of the action's git work.
Revised: the action's version/publish now invoke node .github/scripts/release.mjs, a non-gated entrypoint that runs pnpm install --no-frozen-lockfile (not gated) and then pnpm changeset version|publish (gated, now passes). The earlier workflow step is removed.
On --lockfile-only: I kept a full non-frozen install rather than --lockfile-only. verifyDepsBeforeRun also checks node_modules consistency, not just the lockfile's overrides field, and changeset version/publish run afterward against node_modules; a lockfile-only refresh would leave node_modules unreconciled. The release job isn't latency-sensitive and already did a non-frozen install in the old changeset:version script, so this isn't a regression.
…a prior step changesets/action does git checkout changeset-release/main + git reset --hard right before running the version/publish command, so any reconcile placed in an earlier workflow step is discarded by that reset and the gated 'pnpm changeset' call still trips verifyDepsBeforeRun. Move the non-frozen install into a single non-gated node entrypoint the action invokes, so it runs after the action's git work and immediately before the gated pnpm call. Drop the now-unreferenced changeset:version script.
changeset publish rebuilds packages via prepublishOnly at pack time. A non-frozen reconcile could re-resolve in-range transitive deps so shipped bits diverge from tested bits. prefer-frozen reconciles the deps state to satisfy verifyDepsBeforeRun without re-resolving when the lockfile is satisfiable, and falls back to a full install (e.g. after changeset version bumps workspace versions) when it isn't.
|
/review |
| // prefer-frozen avoids re-resolving deps when the lockfile is already | ||
| // satisfiable (so published packages, which rebuild via prepublishOnly, | ||
| // match what was tested) but falls back to a full install when it isn't. | ||
| run(["install", "--prefer-frozen-lockfile"]); |
There was a problem hiding this comment.
Nit (low): The PR description and the response to copilot both state this script runs pnpm install --no-frozen-lockfile, but the final committed code (last commit ee2a06e) uses --prefer-frozen-lockfile. The flag change is intentional and arguably better — the existing comment at lines 14–16 already explains why prefer-frozen is preferred — but the PR body is now stale. Worth updating the PR description before merge so the release-notes / git-blame trail for this fix matches what actually shipped; otherwise the next person who hits a related release failure will read the PR body, see --no-frozen-lockfile, and be confused when git show disagrees.
Also worth a sanity check: the failure mode the PR is fixing is ERR_PNPM_VERIFY_DEPS_BEFORE_RUN: Setting overrides of lockfile ... is outdated. --prefer-frozen-lockfile short-circuits when the lockfile is satisfiable — which is exactly the state where the outdated-overrides-hash error can still fire (lockfile content fine, recorded metadata stale). If pnpm's frozen path skips rewriting the lockfile's overrides hash, the gated pnpm changeset version immediately after could still hit the same error. The non-frozen install in the previous commit was guaranteed to refresh that hash; prefer-frozen is not, depending on pnpm internals. If this hasn't been verified against an actual reproduction of the original failure (or a forced stale-hash repro), --no-frozen-lockfile is the safer choice for the reconcile call specifically — the second install after changeset version can stay prefer-frozen since versions definitely changed.
There was a problem hiding this comment.
Good objection — and it outranks the finding that motivated the prefer-frozen switch, so I've reverted both installs to --no-frozen-lockfile (commit 8b49a64).
Reasoning: the entire purpose of this PR is to reliably clear ERR_PNPM_VERIFY_DEPS_BEFORE_RUN. --no-frozen-lockfile is pnpm's documented remediation and unconditionally refreshes the deps-state/overrides hash. --prefer-frozen-lockfile's fast path is gated by a satisfiability check whose inclusion of the settings hash I was reasoning about, not verifying — your third case (lockfile content satisfiable, recorded metadata stale → fast path skips the rewrite → gate still fires) is exactly the failure class this fixes. Wrong call = releases stay broken, so I'm not trading a guaranteed fix for an unverified internal assumption.
The shipped-vs-tested concern that prefer-frozen was meant to mitigate was rated low-probability / pre-existing / "not a blocker, worth a conversation" in the adversarial pass, and in practice is negligible: when the committed lockfile is satisfiable, --no-frozen-lockfile does not re-resolve — it only reconciles the genuinely-drifted bits, which is precisely what we want before the prepublishOnly rebuild. Captured as a known follow-up rather than a reason to weaken the fix.
PR body already says --no-frozen-lockfile, so it now matches the code again. The // Must be --no-frozen-lockfile, not --prefer-frozen-lockfile comment in release.mjs records this so it isn't "optimized" back.
|
Review posted successfully. Summary of the review:
|
| // prefer-frozen avoids re-resolving deps when the lockfile is already | ||
| // satisfiable (so published packages, which rebuild via prepublishOnly, | ||
| // match what was tested) but falls back to a full install when it isn't. | ||
| run(["install", "--prefer-frozen-lockfile"]); |
There was a problem hiding this comment.
Reverted to --no-frozen-lockfile in 8b49a64 — code and PR description now agree. --no-frozen-lockfile unconditionally refreshes the deps-state hash that triggers ERR_PNPM_VERIFY_DEPS_BEFORE_RUN, which is the guarantee this PR needs; see the detailed reply on the ask-bonk thread.
The PR's purpose is to reliably clear ERR_PNPM_VERIFY_DEPS_BEFORE_RUN. --no-frozen-lockfile is pnpm's documented remediation and unconditionally refreshes the deps-state hash. --prefer-frozen-lockfile's fast path is gated by a satisfiability check that may not include the settings hash, so it could skip the rewrite in exactly the stale-metadata case this fixes. The shipped-vs-tested concern that motivated prefer-frozen is low-probability, pre-existing, and negligible when the lockfile is satisfiable (no re-resolution occurs).
main is at 0.13.0 (bumped by the #1096 release-PR merge) but 0.13.0 was never published: the verify-deps release bug (fixed in #1104) made the post-merge run open a 0.13.1 PR instead of publishing. These two changesets remained on main, keeping changesets/action on the version path. Removing them makes the next Release run take the publish path and ship the already-versioned 0.13.0 with its full changelog, tags, and GitHub releases. They are restored in the follow-up 0.13.1 PR.
What does this PR do?
Fixes the Release workflow silently failing to publish.
pnpm-workspace.yamlsetsverifyDepsBeforeRun: error, which gates everypnpm run/pnpm <bin>call.changesets/actionrunsgit checkout changeset-release/main+git reset --hard <main>and then invokes its version/publish command. After that git reset, the lockfile state can be inconsistent enough that the gatedpnpm changeset version/pnpm changeset publishcall aborts withERR_PNPM_VERIFY_DEPS_BEFORE_RUN: Setting overrides of lockfile ... is outdated— before anything is versioned or published.This is what broke the last release: the post-merge runs for #1100/#1101/#1102 all failed at the changesets step, the release PR merged without those changesets folded in,
0.13.0was bumped onmainbut never published, and the bot opened a fresh0.13.1release PR (#1103) instead.A reconcile cannot be a prior workflow step: the action's
git reset --hardruns after all workflow steps and discards a working-tree lockfile fix. It has to happen after the action's git work and immediately before the gatedpnpmcall.This PR routes the action's
version/publish(and the manual-publish step) throughnode .github/scripts/release.mjs— a single non-gated entrypoint that runspnpm install --no-frozen-lockfile(not gated byverifyDepsBeforeRun) and thenpnpm changeset version|publish(gated, now passing). The now-unreferencedchangeset:versionpackage script is removed so there's a single release path.verifyDepsBeforeRun: erroris unchanged for local dev and normal CI.Workflow/tooling only; no application code touched.
Closes #
Type of change
Checklist
pnpm typecheckpasses (N/A — CI/tooling only, no TS changed)pnpm lintpasses (N/A — CI/tooling only)pnpm testpasses (N/A — CI/tooling only)pnpm formathas been run (release.mjs uses tab indentation per repo style)AI-generated code disclosure
Screenshots / test output
Failing release run that motivated this — the
Create Release Pull Request or Publishstep: