Skip to content

fix(vendor): write pnpm overrides to pnpm-workspace.yaml for pnpm >= 11 - #174

Open
Mikola Lysenko (mikolalysenko) wants to merge 2 commits into
mainfrom
fix/pnpm11-overrides-location
Open

fix(vendor): write pnpm overrides to pnpm-workspace.yaml for pnpm >= 11#174
Mikola Lysenko (mikolalysenko) wants to merge 2 commits into
mainfrom
fix/pnpm11-overrides-location

Conversation

@mikolalysenko

@mikolalysenko Mikola Lysenko (mikolalysenko) commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

Problem

In vendored mode for the npm/pnpm ecosystem, the CLI pinned the vendored tarball by writing a pnpm.overrides entry into package.json. pnpm >= 11 no longer reads overrides from the package.json pnpm field — it moved to pnpm-workspace.yaml (https://pnpm.io/settings). So pnpm 11 ignored the override, resolved no overrides from config, and a frozen install of the committable artifact refused:

[WARN] The "pnpm" field in package.json is no longer read by pnpm. ... "pnpm.overrides".
[ERR_PNPM_LOCKFILE_CONFIG_MISMATCH] Cannot proceed with the frozen installation.
  The current "overrides" configuration doesn't match the value found in the lockfile

The vendored tarball + lock were correct (they even pass pnpm 11's tarball-URL supply-chain policy) — only the override location was wrong. Surfaced by PR #169's real-production vendored e2e (e2e_vendored_production.rs, pnpm leg) and documented in docs/testing/vendored-production-e2e.md.

Reproduced on this host (pnpm 11.21.0): pnpm add minimist@1.2.2scan --mode vendored → copy only the committable files → cold pnpm install --frozen-lockfile --offlineERR_PNPM_LOCKFILE_CONFIG_MISMATCH.

Fix

The pnpm vendor backend now mirrors the same versioned <name>@<version>file:<rel-tgz> selector into pnpm-workspace.yaml, alongside the existing package.json override, so whichever surface the installed pnpm reads matches the lock's overrides: section (what pnpm compares for the mismatch):

  • no workspace file → create one with a root-only packages: ['.'] list (pnpm 9 refuses a workspace file whose packages field is missing/empty; . is already the sole importer and, unlike packages/*, cannot glob a stray packages/ subtree into the workspace) plus the overrides: block;
  • existing file, no overrides: → append the section, packages: untouched;
  • existing overrides: section → insert our key (or take over a user's exact-version pin), fail-closed on a conflicting same-name override or an inline/flow mapping.

package.json pnpm.overrides is kept for pnpm 9/10 back-compat. Edits are line splices (byte-stable), tracked with wiring records + PnpmMeta flags. The three surfaces are committed override-first / lock-last so a lock-write failure never strands a desynced override. vendor --revert deletes a file it created (when still the bare scaffold) or splices its override back out of one it edited (byte-for-byte), restoring a taken-over value and dropping an emptied section it created. Scoped to the pnpm vendored path; no other ecosystem touched.

Test

Verified on real pnpm 9.15.9 / 10.34.5 / 11.21.0: a cold pnpm install --frozen-lockfile --offline from only the committable files installs the vendored (patched) minimist@1.2.2 bytes — no ERR_PNPM_LOCKFILE_CONFIG_MISMATCH — and vendor --revert removes the created pnpm-workspace.yaml and restores package.json/pnpm-lock.yaml.

  • New hermetic unit tests (vendor::pnpm_lock): create-scaffold + revert-deletes; append-section + byte-restore; insert-beside-existing + remove-only-ours; inline-mapping refused; conflicting-override refused; commit unwind deletes a created workspace file. state.rs round-trip covers the new PnpmMeta fields. All 602 vendor::* lib tests pass.
  • Capstone e2e_vendor_pnpm_build.rs (real corepack pnpm@10/9): now asserts the workspace file is created with the override + root-only packages:, is copied into the fresh-checkout proof, stays byte-stable on re-vendor, and is deleted on revert. Passes.
  • Production e2e e2e_vendored_production.rs (real Socket production + real npm registry, host pnpm 11): the pnpm leg's xfail workaround is removed — the frozen install now succeeds directly and byte-checks the patched bytes. pnpm_vendored_install_proof passes.
  • cargo build + cargo clippy clean (the only clippy warnings are pre-existing doc_lazy_continuation notes in an unrelated module-doc header, present on main).

🤖 Generated with Claude Code


Note

Medium Risk
Changes pnpm vendoring commit/revert behavior and creates or edits pnpm-workspace.yaml, which affects install correctness across pnpm 9–11; scope is limited to the pnpm vendor path with extensive tests.

Overview
Fixes pnpm >= 11 frozen installs failing with ERR_PNPM_LOCKFILE_CONFIG_MISMATCH because overrides were only written to package.json pnpm.overrides, while pnpm 11 reads them from pnpm-workspace.yaml.

The pnpm vendor backend now mirrors the same versioned <name>@<version>file: override into pnpm-workspace.yaml (still updating package.json for pnpm 9/10). If no workspace file exists, it creates one with a root-only packages: ['.'] list plus overrides: so pnpm 9 accepts the file. Edits use line splices for byte-stable revert; commit_surfaces unwinds override files if the lock write fails.

vendor --revert deletes a scaffold file it created or removes only its override from an edited file. PnpmMeta gains createdWorkspaceFile / createdWorkspaceOverrides. E2E and production tests no longer use a manual workspace workaround; docs mark the pnpm 11 gap as fixed.

Reviewed by Cursor Bugbot for commit a23abb1. Configure here.

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

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Migration wipe of revert wiring
    • Fixed by returning AlreadyPatched when package.json and lock are in sync, preventing replacement of ledger entry that would lose revert wiring records.

Create PR

Or push these changes by commenting:

@cursor push d2439191b3
Preview (d2439191b3)
diff --git a/crates/socket-patch-core/src/vendor/pnpm_lock.rs b/crates/socket-patch-core/src/vendor/pnpm_lock.rs
--- a/crates/socket-patch-core/src/vendor/pnpm_lock.rs
+++ b/crates/socket-patch-core/src/vendor/pnpm_lock.rs
@@ -271,11 +271,14 @@
         Err(e) => return done_failure(purl, format!("{PNPM_WORKSPACE} surgery failed: {e}")),
     };
 
-    if !pkg_changed && !lock_changed && ws_edit.new_text.is_none() {
-        // Everything already carries this uuid + the packed integrity: the
-        // project is in sync. The tarball re-pack above was byte-identical
-        // by determinism; synthesize AlreadyPatched and record nothing (the
-        // existing ledger entry stays authoritative).
+    if !pkg_changed && !lock_changed {
+        // package.json and the lock already carry this uuid + the packed
+        // integrity: the core wiring is in sync. The tarball re-pack above was
+        // byte-identical by determinism. Even if the workspace file (pnpm >= 11
+        // surface) needs first-time creation during migration, that must not
+        // replace the ledger entry and wipe package.json/lock revert wiring.
+        // Synthesize AlreadyPatched and record nothing (the existing ledger
+        // entry stays authoritative).
         return done(
             already_patched_result(purl, &project_root.join(&rel_tgz), &record.files),
             None,

You 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 a23abb1. Configure here.

created_overrides_table,
created_pnpm_table,
created_workspace_file: ws_edit.created_file,
created_workspace_overrides: ws_edit.created_overrides,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Migration wipe of revert wiring

High Severity

When only pnpm-workspace.yaml needs writing (the upgrade path for projects already vendored before this fix), the new ledger entry keeps only the workspace wiring. persist_vendor_entry replaces the prior entry, so package.json and pnpm-lock.yaml originals are dropped and vendor --revert can no longer restore them.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit a23abb1. Configure here.

…pm >= 11

pnpm 11 stopped reading `overrides` from package.json's `pnpm` field — it
moved to `pnpm-workspace.yaml` (https://pnpm.io/settings). The pnpm vendor
backend wrote only `package.json` `pnpm.overrides`, so pnpm 11 ignored it and
a frozen install of the committable artifact refused with
`ERR_PNPM_LOCKFILE_CONFIG_MISMATCH` — the lock carried an `overrides:` section
but pnpm resolved none from config. The vendored tarball and lock were correct
(they even pass pnpm 11's tarball-URL supply-chain policy); only the override
LOCATION was wrong.

Vendoring now mirrors the same versioned `<name>@<version>` → `file:` selector
into `pnpm-workspace.yaml` alongside the existing package.json override, so
whichever surface the installed pnpm reads matches the lock's `overrides:`
section:

- no workspace file → create one with a root-only `packages: ['.']` list
  (pnpm 9 refuses a workspace file whose `packages` field is missing/empty; `.`
  is the sole importer already and cannot glob a stray `packages/` subtree into
  the workspace the way `packages/*` would) plus the `overrides:` block;
- existing file without `overrides:` → append the section (packages untouched);
- existing `overrides:` section → insert our key (or take over a user's exact
  pin), fail-closed on a conflicting same-name override or an inline mapping.

package.json `pnpm.overrides` is kept for pnpm 9/10. Verified across real pnpm
9.15.9 / 10.34.5 / 11.21.0: a cold `pnpm install --frozen-lockfile --offline`
from only the committable files installs the vendored (patched) bytes with no
config mismatch. `vendor --revert` deletes a file it created (when still the
bare scaffold) or splices its override back out of one it edited; the three
surfaces are committed override-first / lock-last so a lock-write failure never
leaves a desynced override behind.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… surface

The pnpm >= 11 support (#174) mirrors the tarball override into
`pnpm-workspace.yaml` in addition to `package.json` + `pnpm-lock.yaml`. On the
UPGRADE path — a project vendored by the pre-workspace code, then re-vendored
under the new code — `package.json` and `pnpm-lock.yaml` already carry the
override, so `vendor_pnpm` rewrites neither and its fresh `VendorEntry.wiring`
names ONLY the newly created `pnpm-workspace.yaml` surface.

The CLI vendor flow re-invokes the backend on every installed package (there is
no ledger short-circuit; the `already_vendored` classification only happens
AFTER the backend runs and returns `AlreadyPatched`). So `persist_vendor_entry`
would replace the prior ledger entry wholesale with the workspace-only entry,
dropping the `package.json` + `pnpm-lock.yaml` pre-vendor originals the FIRST
vendoring recorded. `vendor --revert` could then restore only the workspace
file, leaving the override wired into the other two surfaces forever.

Fix: extract the re-vendor reconciliation into `vendor::carry_forward_wiring`
and extend it to (1) union the prior entry's wiring records for surfaces THIS
run left in sync, and (2) OR-merge the pnpm "created this table/file/section"
bookkeeping — both scoped to a same-uuid re-vendor so a new-uuid re-vendor
still rewires every surface fresh. The existing original-fill and go-takeover
carry-forward are unchanged. Revert now byte-restores all three surfaces on the
upgrade path.

Regression test `revendor_upgrade_adds_workspace_and_revert_restores_all_three_surfaces`
stages a pre-workspace vendored project, re-vendors (adding only the workspace
mirror), reconciles, then reverts and asserts package.json + pnpm-lock.yaml +
pnpm-workspace.yaml are all byte-restored to their pre-vendor originals.

Co-Authored-By: Claude Opus 4.8 <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.

2 participants