[agent] Found by the scheduled pnpm bug-hunt routine (ledger #303).
Summary
The #557 fix (#818, e889d6d) decides whether a restored pnpm-lock.yaml resolution gets a tarball: URL from PnpmTarballPolicy. That policy reads lockfileIncludeTarballUrl from pnpm-workspace.yaml, falling back to lockfile-include-tarball-url in .npmrc, without checking which of those files the project's pnpm actually reads:
- pnpm 9 ignores settings in
pnpm-workspace.yaml, so lockfileIncludeTarballUrl: true there has no effect, and the lock pnpm 9 writes has no tarball: fields.
- pnpm 11 and 12 ignore
lockfile-include-tarball-url in .npmrc. Again, the lock has no tarball: fields.
In both cases hosted rollback (and remove) treats the setting as active, so it writes tarball: https://registry.npmjs.org/<name>/-/<name>-<ver>.tgz into every restored resolution. The lock no longer matches its pre-hosted bytes. pnpm keeps the extra field, and a later plain pnpm install doesn't remove it, so the change persists.
Impact
Repro (Linux, main 9c43dfc, local mock of the patch API, SOCKET_NPM_REGISTRY pointed at a mirror that serves the real npmjs dist)
mkdir tb && cd tb
echo '{"name":"tb","version":"1.0.0","dependencies":{"left-pad":"1.3.0"}}' > package.json
# pnpm 11 / 12 variant:
echo 'lockfile-include-tarball-url=true' > .npmrc
# (pnpm 9 variant instead: printf 'packages:\n - .\nlockfileIncludeTarballUrl: true\n' > pnpm-workspace.yaml)
pnpm install # lock has NO tarball: field (pnpm ignores the setting)
cp pnpm-lock.yaml lock.orig
socket-patch scan --mode hosted --yes
socket-patch rollback --yes # status success
diff lock.orig pnpm-lock.yaml
# > resolution: {integrity: sha512-XI5M…CoEA==, tarball: https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz}
socket-patch remove pkg:npm/left-pad@1.3.0 instead of rollback gives the same diff.
Expected vs actual
Matrix (each row run twice, in fresh projects)
| pnpm |
setting location |
lock after pnpm install has tarball: |
rollback byte-exact |
| 9.15.9 |
pnpm-workspace.yaml lockfileIncludeTarballUrl: true |
no (ignored) |
no |
| 9.15.9 |
.npmrc (control) |
yes |
yes |
| 10.34.5 |
pnpm-workspace.yaml / .npmrc (control) |
yes / yes |
yes / yes |
| 11.28.3 |
.npmrc |
no (ignored) |
no |
| 12.8.1 |
.npmrc |
no (ignored) |
no |
| 12.8.1 |
pnpm-workspace.yaml (control) |
yes |
yes |
Linux only; the logic is OS-independent. I couldn't use release 4.0.0 as a baseline, because its hosted rollback can't unwind against the mock. Before #818 the restore never wrote tarball: at all (that was #557), so this exact symptom starts with e889d6d.
Suspect code
crates/socket-patch-core/src/patch/redirect/upstream/npm.rs:735 (pnpm_tarball_policy): always (:758) honors the workspace-file key and the .npmrc key whatever the pnpm major. A safer signal is the lock itself: if any unpinned resolution: in the same lock already carries a tarball: that the registry could derive, the setting is active. With no such evidence, keep the restore bare. (pnpm 9 locks are lockfileVersion: '9.0' too, so the lock version alone can't tell 9 from 10+.)
- Used at
:842 in restore_pnpm_locks.
Probe runs: none (Linux reproduction only).
[agent] Found by the scheduled pnpm bug-hunt routine (ledger #303).
Summary
The #557 fix (#818,
e889d6d) decides whether a restoredpnpm-lock.yamlresolution gets atarball:URL fromPnpmTarballPolicy. That policy readslockfileIncludeTarballUrlfrompnpm-workspace.yaml, falling back tolockfile-include-tarball-urlin.npmrc, without checking which of those files the project's pnpm actually reads:pnpm-workspace.yaml, solockfileIncludeTarballUrl: truethere has no effect, and the lock pnpm 9 writes has notarball:fields.lockfile-include-tarball-urlin.npmrc. Again, the lock has notarball:fields.In both cases hosted
rollback(andremove) treats the setting as active, so it writestarball: https://registry.npmjs.org/<name>/-/<name>-<ver>.tgzinto every restored resolution. The lock no longer matches its pre-hosted bytes. pnpm keeps the extra field, and a later plainpnpm installdoesn't remove it, so the change persists.Impact
tarball:URL from locks written withlockfileIncludeTarballUrl, so the restore isn't byte-exact #557 was fixed to guarantee. Users get an unexplained diff inpnpm-lock.yamlafter undoing hosted mode.registry.npmjs.orgin the lock, where pnpm would otherwise derive the URL from the configured registry. I haven't reproduced it, but a project that later pointsregistry=at an internal mirror would then still fetch these packages from npmjs.tarball:URL from locks written withlockfileIncludeTarballUrl, so the restore isn't byte-exact #557 fix.Repro (Linux, main
9c43dfc, local mock of the patch API,SOCKET_NPM_REGISTRYpointed at a mirror that serves the real npmjsdist)socket-patch remove pkg:npm/left-pad@1.3.0instead ofrollbackgives the same diff.Expected vs actual
tarball:URL from locks written withlockfileIncludeTarballUrl, so the restore isn't byte-exact #557/Fix hosted restore dropping registry tarball URLs (#557, #817) #818 made the pnpm restore byte-exact for locks with and without tarball URLs.tarball:field.Matrix (each row run twice, in fresh projects)
pnpm installhastarball:pnpm-workspace.yamllockfileIncludeTarballUrl: true.npmrc(control)pnpm-workspace.yaml/.npmrc(control).npmrc.npmrcpnpm-workspace.yaml(control)Linux only; the logic is OS-independent. I couldn't use release 4.0.0 as a baseline, because its hosted rollback can't unwind against the mock. Before #818 the restore never wrote
tarball:at all (that was #557), so this exact symptom starts withe889d6d.Suspect code
crates/socket-patch-core/src/patch/redirect/upstream/npm.rs:735(pnpm_tarball_policy):always(:758) honors the workspace-file key and the.npmrckey whatever the pnpm major. A safer signal is the lock itself: if any unpinnedresolution:in the same lock already carries atarball:that the registry could derive, the setting is active. With no such evidence, keep the restore bare. (pnpm 9 locks arelockfileVersion: '9.0'too, so the lock version alone can't tell 9 from 10+.):842inrestore_pnpm_locks.Probe runs: none (Linux reproduction only).