[agent] Found by the scheduled pnpm bug-hunt routine (ledger #303).
Summary
On a pnpm project with no pnpm.overrides and no pnpm-workspace.yaml, vendoring two or more packages and then running vendor --revert (or rollback) restores pnpm-lock.yaml byte-exactly. But it leaves "pnpm": { "overrides": {} } in package.json. On a lockfile 9.0 project (pnpm 9–12) it also leaves a pnpm-workspace.yaml that vendoring created (packages: ['.'] plus an empty overrides: key). With exactly one vendored package the revert is byte-exact. Release 4.0.0 is byte-exact with two packages too, so this is a regression.
Impact
After a full revert the checkout isn't back to its pre-vendor state. Two tracked files are dirty (one of them new) and need a manual cleanup commit. The leftover pnpm-workspace.yaml also turns a single-package project into a workspace root as far as pnpm is concerned. Frozen installs still pass and the lock is untouched, so this isn't a supply-chain break. It's a "revert leaves residue" bug: the contract for vendor --revert is to "restore recorded original lockfile fragments", and the backend tracks createdPnpmTable / createdOverridesTable / createdWorkspaceFile precisely so that it can delete what it created.
Repro (main 045d7ec, Linux, Node 22)
mkdir app && cd app
cat > package.json <<'EOF'
{
"name": "fx",
"version": "1.0.0",
"private": true,
"dependencies": {
"left-pad": "1.3.0",
"is-number": "7.0.0"
}
}
EOF
pnpm install # pnpm 12.8.1
# stage .socket/manifest.json + blobs with patches for both packages
socket-patch vendor --json # success, applied 2
socket-patch vendor --revert --json # success
git diff / ls # see below
The result after the revert (pnpm 12.8.1; pnpm-lock.yaml is byte-identical to the original):
--- package.json (original)
+++ package.json (after vendor --revert)
@@
"is-number": "7.0.0"
+ },
+ "pnpm": {
+ "overrides": {}
}
}
--- /dev/null
+++ pnpm-workspace.yaml
+packages:
+ - '.'
+overrides:
socket-patch rollback gives the same result. On pnpm 7/8 (legacy 5.4 / 6.0 locks) only the package.json residue appears, because the legacy dialect doesn't mirror overrides into a workspace file.
Why
The ledger records the "created" flags only on the entry that was vendored first (.socket/vendor/state.json):
pkg:npm/is-number@7.0.0 pnpm: {createdOverridesTable: true, createdPnpmTable: true, createdWorkspaceFile: true}
pkg:npm/left-pad@1.3.0 pnpm: {}
The revert then processes entries in that same order (is-number, then left-pad). When the creator entry is reverted, left-pad's key still keeps the table and the workspace file non-empty, so nothing is removed. When left-pad is reverted last and empties them, it has no "created" flags, so it keeps them (crates/socket-patch-core/src/vendor/pnpm_lock.rs:901-930 for package.json, and :961-985 → revert_workspace for pnpm-workspace.yaml). Removing the entries one at a time with remove <purl> in the opposite order (left-pad first, then is-number) cleans up correctly, which confirms that it depends on order.
Expected vs actual
- Expected (and what 4.0.0 does): after the last vendored entry is reverted, a
pnpm.overrides table, pnpm table and pnpm-workspace.yaml that vendoring created are removed, so package.json and the workspace come back byte-exact. That's what the pnpm backend's created-table tracking is for, and the single-package revert already does it.
- Actual: they're kept whenever more than one entry shared them.
| OS |
pnpm 7.33.7 (5.4) |
8.15.9 (6.0) |
9.15.9 |
10.34.5 |
11.28.3 |
12.8.1 |
| Linux, 2 packages |
package.json residue |
package.json residue (also with 3 packages) |
package.json + workspace residue |
package.json + workspace residue |
package.json + workspace residue |
package.json + workspace residue (also with 3 packages, and via rollback) |
| Linux, 1 package |
byte-exact |
byte-exact |
byte-exact |
byte-exact |
untested |
byte-exact |
| macOS / Windows |
untested (probe branches blocked) |
|
|
|
|
|
Every 2-package cell reproduced at least twice on main, and a --frozen-lockfile install after the revert passes in every cell.
Release 4.0.0 (npm @socketsecurity/socket-patch@4.0.0), same fixture with 2 packages: byte-exact on 8.15.9 and 12.8.1, with the same ledger flags and the same revert event order.
First bad commit
Bisected on pnpm 12.8.1 with the two-package fixture: 09956d90 "Cleanup: no .socket residue, locks that never outlive a command, manifest-free vendored mode (#247)" is the first bad commit. Its parent 9489b185 (#246) is good, and every commit tested between it and main is bad. The recent pnpm vendoring refactor (#583, fc356c0) didn't introduce it: b8bf049, just before it, is already bad.
[agent] Found by the scheduled pnpm bug-hunt routine (ledger #303).
Summary
On a pnpm project with no
pnpm.overridesand nopnpm-workspace.yaml, vendoring two or more packages and then runningvendor --revert(orrollback) restorespnpm-lock.yamlbyte-exactly. But it leaves"pnpm": { "overrides": {} }inpackage.json. On a lockfile 9.0 project (pnpm 9–12) it also leaves apnpm-workspace.yamlthat vendoring created (packages: ['.']plus an emptyoverrides:key). With exactly one vendored package the revert is byte-exact. Release 4.0.0 is byte-exact with two packages too, so this is a regression.Impact
After a full revert the checkout isn't back to its pre-vendor state. Two tracked files are dirty (one of them new) and need a manual cleanup commit. The leftover
pnpm-workspace.yamlalso turns a single-package project into a workspace root as far as pnpm is concerned. Frozen installs still pass and the lock is untouched, so this isn't a supply-chain break. It's a "revert leaves residue" bug: the contract forvendor --revertis to "restore recorded original lockfile fragments", and the backend trackscreatedPnpmTable/createdOverridesTable/createdWorkspaceFileprecisely so that it can delete what it created.Repro (main
045d7ec, Linux, Node 22)The result after the revert (pnpm 12.8.1;
pnpm-lock.yamlis byte-identical to the original):socket-patch rollbackgives the same result. On pnpm 7/8 (legacy 5.4 / 6.0 locks) only thepackage.jsonresidue appears, because the legacy dialect doesn't mirror overrides into a workspace file.Why
The ledger records the "created" flags only on the entry that was vendored first (
.socket/vendor/state.json):The revert then processes entries in that same order (
is-number, thenleft-pad). When the creator entry is reverted,left-pad's key still keeps the table and the workspace file non-empty, so nothing is removed. Whenleft-padis reverted last and empties them, it has no "created" flags, so it keeps them (crates/socket-patch-core/src/vendor/pnpm_lock.rs:901-930forpackage.json, and:961-985→revert_workspaceforpnpm-workspace.yaml). Removing the entries one at a time withremove <purl>in the opposite order (left-padfirst, thenis-number) cleans up correctly, which confirms that it depends on order.Expected vs actual
pnpm.overridestable,pnpmtable andpnpm-workspace.yamlthat vendoring created are removed, sopackage.jsonand the workspace come back byte-exact. That's what the pnpm backend's created-table tracking is for, and the single-package revert already does it.rollback)Every 2-package cell reproduced at least twice on main, and a
--frozen-lockfileinstall after the revert passes in every cell.Release 4.0.0 (npm
@socketsecurity/socket-patch@4.0.0), same fixture with 2 packages: byte-exact on 8.15.9 and 12.8.1, with the same ledger flags and the same revert event order.First bad commit
Bisected on pnpm 12.8.1 with the two-package fixture:
09956d90"Cleanup: no .socket residue, locks that never outlive a command, manifest-free vendored mode (#247)" is the first bad commit. Its parent9489b185(#246) is good, and every commit tested between it and main is bad. The recent pnpm vendoring refactor (#583,fc356c0) didn't introduce it:b8bf049, just before it, is already bad.