Repository navigation
Vendored pnpm 7–11 scan exits 1 after pnpm remove (or an upgrade) because it tries to vendor the orphaned node_modules/.pnpm/<name>@<ver> that pnpm keeps for 7 days, and the suggested pnpm install doesn't clear it #1197
Description
[agent] Found by the scheduled pnpm bug-hunt routine (ledger #303).
Summary
pnpm 7–11 keep a removed package's virtual-store directory (node_modules/.pnpm/left-pad@1.3.0) after pnpm remove / an upgrade until modules-cache-max-age expires (default 7 days). Nothing links to it any more, and the lock no longer has the package. The npm crawler still walks every in-project .pnpm entry, so scan --mode vendored discovers the orphan, asks for its patch, and fails it with vendor_lock_entry_not_found. The run ends partial_failure, exit 1, on every re-run.
The error tells the user to run pnpm install, which does not remove the orphan. pnpm prune doesn't either. Only rm -rf node_modules && pnpm install (or waiting a week) makes the vendored scan green again. Hosted mode in the same tree exits 0: it reports the orphan as an unpinned / redirect_unconfirmed row and leaves the lock alone. pnpm 12 drops the orphan itself, so it isn't affected.
Impact
A developer who removes or upgrades a patched dependency and re-runs the vendored scan gets a red exit code and a remedy that doesn't work, for up to a week. Scripts and pre-commit hooks that gate on the scan's exit code fail. The other packages are still vendored correctly. Fresh CI checkouts (no node_modules) aren't affected.
Repro (Linux, Node 22, main 03b9418)
The mock patch API serves left-pad@1.3.0 and is-number@7.0.0 (batch, by-package, view, package grant, tarball), using the same contract as tests/e2e_redirect_pnpm_build.rs.
mkdir app && cd app
echo '{"name":"root","version":"1.0.0","dependencies":{"left-pad":"1.3.0","is-number":"7.0.0"}}' > package.json
pnpm install # pnpm 9.15.9
pnpm remove left-pad
ls node_modules/.pnpm # is-number@7.0.0 left-pad@1.3.0 lock.yaml <- orphan kept
socket-patch scan --mode vendored --yes --api-url $MOCK --org test-org --api-token fake
# [skip] pkg:npm/is-number@7.0.0 (already vendored) (or vendored on the first run)
# [error] pkg:npm/left-pad@1.3.0 (vendor_lock_entry_not_found): pnpm-lock.yaml has no packages
# entry for left-pad@1.3.0 — make sure the package is installed and locked (`pnpm install`)
# exit 1 (--json: status partial_failure, download.patches[left-pad].action failed)
pnpm install && socket-patch scan --mode vendored ... # still exit 1, the orphan is still there
pnpm prune && socket-patch scan --mode vendored ... # still exit 1
rm -rf node_modules && pnpm install && socket-patch scan --mode vendored ... # exit 0
socket-patch scan --mode hosted --dry-run ... # exit 0 in the orphaned treeThe same happens when the package was vendored before pnpm remove. vendor --check then correctly says "dependency removed … run socket-patch scan --mode vendored --prune". That prune does revert the entry, but on 7–11 it still exits 1, because the same pass tries to vendor the orphan again.
Expected vs actual
- Expected: the vendored scan should vendor what the lockfile installs. CLI_CONTRACT
scan --prune(b) treats "no longer in the lockfile graph" as removed, and the hosted engine reports a crawled package that the lock doesn't pin asunpinned(exit 0). An installed-but-unreachable store entry should be skipped (at most with a warning), not fail the run. An error remedy should also be one that clears the error. - Actual: exit 1 /
partial_failureon every run while the orphan exists, andpnpm install, the remedy given, has no effect.
OS × version
Linux, Node 22, real pnpm installs. Each row is a fresh project, install, pnpm remove left-pad, then scan --mode vendored --json --yes.
| pnpm | orphan kept | vendored exit | hosted --dry-run exit |
|---|---|---|---|
| 7.33.7 | yes | 1 (vendor_lock_entry_not_found) |
0 |
| 8.15.9 | yes | 1 | 0 |
| 9.15.9 | yes | 1 (reproduced 3×, also after a prior vendor and with --prune) |
0 |
| 10.34.6 | yes | 1 (2×) | 0 |
| 11.28.5 | yes | 1 | 0 |
| 12.10.1 | no | 0 | 0 |
macOS and Windows weren't probed. The behaviour is in the crawler and the vendor preflight, not in OS paths. Release 4.0.0 couldn't be compared, because its API contract differs from the v5 mock (it exits error before vendoring), so I didn't bisect.
Suspect code
crates/socket-patch-core/src/crawlers/npm_crawler.rs:517: the in-project.pnpmstore layout is walked entry by entry, with no reachability filter. Comparereachable_pnpm_global_virtual_store_entries_sync(:700), which the global virtual store already uses, ornode_modules/.pnpm/lock.yaml/.modules.yaml, which list what is live.crates/socket-patch-core/src/vendor/pnpm_lock.rs:663(andpnpm_lock_legacy.rs:319for 7/8):vendor_lock_entry_not_foundis a hard refusal that counts as a failure, even when the lock simply doesn't install the package any more.
No probe runs.
Activity
mikolalysenko commentedon Oct 9, 2026
[agent] Follow-up from the pnpm bug-hunt routine (ledger #303): pnpm 12 is affected too, through the upgrade path. The table above only covered
pnpm remove, which on 12 drops the orphan itself.Main
f3c6313, Linux, Node 22, pnpm 12.10.1, local mock of the patch API servingleft-pad@1.3.0only (purl-aware: no patch for 1.2.0):pnpm add -E left-pad@1.2.0pnpm installpnpm prunepnpm install, then upgrade (2×)left-pad@1.3.0andleft-pad@file+.socket+vendor+…)Hosted
--dry-runexits 0 in the same tree. 9.15.9 with the plain upgrade also gives exit 1, as the issue describes.So on 12
pnpm pruneis a working workaround, but the error's own remedy (pnpm install) still isn't. The reachability filter suggested in the issue (node_modules/.pnpm/lock.yaml, which no longer lists 1.3.0) would cover 12 as well.Generated by Claude Code
mikolalysenko commentedon Oct 9, 2026
v5 release blocker (P1). After an ordinary pnpm removal/upgrade, stale store entries must not stop a scan of the current lockfile or produce an ineffective install remedy.
This follows the maintainer's release scope: one normally completing CLI instance, prioritizing valid-lockfile patch/install behavior, compatibility, and actionable CLI UX.
mikolalysenko commentedon Oct 9, 2026
[agent] Claiming for v5 blocker burn-down (shared root cause: npm crawler walks unreachable node_modules/.pnpm entries and vendored preflight hard-fails them). Branch: agent/v5-pnpm-orphan-store. Claim-ID: 2026-10-09T16:41:59Z-ee1d7e
Skip pnpm store entries the install dropped