Repository navigation
After npm install <pkg>@<other version> moves a vendored npm package off its patched version, scan --prune, vendor --revert, remove and rollback all drift-keep it, so vendor --check stays red and every remedy it names loops #1155
Description
[agent] Found by the scheduled npm bug-hunt routine (ledger #302).
Summary
Vendor a package, then upgrade or downgrade it with npm (npm install ms@2.1.3 over a vendored ms@2.1.2). The lock entry node_modules/ms now resolves the new version from the registry, and nothing references .socket/vendor/npm/<uuid>/ any more. After that:
vendor --checkexits 1:dependency removed: no lockfile resolves pkg:npm/ms@2.1.2 any more (it was upgraded or uninstalled) … run socket-patch scan --mode vendored --prune to revert the vendored entry.scan --mode vendored --pruneexits 0 and reverts nothing.--jsonshowsgc.keptVendoredEntries: ["pkg:npm/ms@2.1.2"]. The human output just says "No patches available" (that's the Humanscan --mode vendored --prunesilently skips the vendored GC when no remaining package has a patch, so annpm uninstalled vendored entry is never reverted (exit 0), while--jsonreverts it andvendor --checkkeeps pointing at that same command #1127 gap; with another patched package still installed it prints the "kept 1 drifted vendored entry … undo the drift" line instead).vendor --revertexits 0:lock entry node_modules/ms was re-resolved since vendoring (now resolved to https://registry.npmjs.org/ms/-/ms-2.1.3.tgz); left aloneandKept 1 drifted package … undo the drift and re-run vendor --revert to finish.remove pkg:npm/ms@2.1.2exits 1:1 matching entry was drift-kept …; re-run scan --mode vendored to normalize, then remove again. Re-running the scan changes nothing.rollback pkg:npm/ms@2.1.2exits 1:Kept vendored state … lockfile wiring drifted.vendor --checkis still 1.
"Undo the drift" would mean downgrading back to the vulnerable version. The only way out is deleting .socket/vendor/npm/<uuid>/ and editing .socket/vendor/state.json by hand.
The Bun routine found this first and handed it to the npm ledger. Bun's text and binary locks behave the same. #1132 (Bun bun.lockb removal), #1140 (uv) and #1142 (Pipenv) are the per-backend removal variants. On npm, a plain npm uninstall is reverted correctly (the vendor_lock_entry_removed arm from #665/#689). Only the re-resolved case loops.
Impact
Any project that upgrades a vendored dependency (the normal way to drop a patch once upstream ships a fix, and something Dependabot or Renovate does by itself) is left with a committed orphan tarball and a ledger entry. vendor --check then fails CI permanently, and none of the remedies socket-patch suggests can clear it. Nothing is installed unpatched (the new version installs from the registry), so this is a stuck-state / CI-gate bug, not a silent unpatching.
Repro (Linux, real npm, local mock of the public patch proxy serving a free patch for ms@2.1.2)
export SOCKET_PROXY_URL=http://127.0.0.1:18732 SOCKET_API_URL=http://127.0.0.1:18732 NO_PROXY=127.0.0.1
git init -q proj && cd proj
echo '{"name":"proj","version":"1.0.0","dependencies":{"ms":"2.1.2"}}' > package.json
npm install && git add -A && git commit -qm init
socket-patch scan --mode vendored && npm install && git add -A && git commit -qm vendored
socket-patch vendor --check; echo $? # 0: committed artifact and wiring verified
npm install ms@2.1.3 # lock: node_modules/ms -> registry ms-2.1.3.tgz
socket-patch vendor --check; echo $? # 1: "dependency removed … upgraded or uninstalled … run scan --mode vendored --prune"
socket-patch scan --mode vendored --prune --json | jq '.gc | {revertedVendoredEntries, keptVendoredEntries}'
# {"revertedVendoredEntries": [], "keptVendoredEntries": ["pkg:npm/ms@2.1.2"]}
socket-patch vendor --revert; echo $? # 0, "Kept 1 drifted package … undo the drift"
socket-patch remove pkg:npm/ms@2.1.2; echo $? # 1, "re-run scan --mode vendored to normalize, then remove again"
socket-patch rollback pkg:npm/ms@2.1.2; echo $? # 1, "lockfile wiring drifted"
socket-patch vendor --check; echo $? # still 1
ls .socket/vendor/npm/ # 22222222-… still thereA downgrade (left-pad 1.3.0 → npm install left-pad@1.2.0) behaves the same.
Expected vs actual
- Expected: CLI_CONTRACT.md:137 says
scan --pruneleg (b) reverts "EVERY ledger entry whose dependency is no longer in the lockfile graph".pkg:npm/ms@2.1.2is no longer in the graph.vendor --check(and the doc comment onunwired_check_failure,crates/socket-patch-cli/src/commands/vendor.rs:396) names "upgraded or uninstalled" andscan --pruneas the fix. So prune, and alsovendor --revert/remove/rollback, should drop the ledger entry and the artifact, leaving the user's upgraded lock entry alone. - Actual: the npm revert classifies the re-resolved entry as
vendor_lock_entry_drifted, sooutcome.drift_skipped()keeps the artifact and ledger entry. Every command reports "kept", andvendor --checkkeeps sending you to the same prune.
Matrix (main c4235a2, Linux, Node 22.22 / Node 24.21 for npm 12)
| OS | npm | lock | upgrade (ms 2.1.2→2.1.3) | downgrade (left-pad 1.3.0→1.2.0) | npm uninstall (control) |
|---|---|---|---|---|---|
| Linux | 8.19.4 | v2 | loops | – | reverted (verified 2026-10-07, ledger #302) |
| Linux | 10.9.4 | v3 | loops (×2) | loops | reverted (--json prune, this run) |
| Linux | 12.2.0 | v3 | loops | – | reverted (verified 2026-10-07, ledger #302) |
(On npm 8 the pre-upgrade vendor --check is already red because of #879. That's unrelated.) macOS and Windows weren't probed: the code path is platform-independent lock JSON handling.
Not bisected: main's history is grafted, so commit bisects aren't possible. The "upgraded or uninstalled … --prune" check text came with #970/#1050.
Suspect code
crates/socket-patch-core/src/vendor/npm_lock.rs:1209-1231(revert_one_record): when the live entry exists but itsresolvedno longer points into our uuid dir, it always warnsvendor_lock_entry_driftedand returns. It doesn't check whether the live entry still names the vendored version. An entry whoseversiondiffers fromrec.original/rec.new(or, more generally, one thatDiscovery::vendor_entry_in_usealready callsSome(false)) is the same "dependency left the graph" case as the removed arm above it (lines 1189-1197), and should be handled likeLOCK_ENTRY_REMOVED_CODE: nothing to restore, artifact releasable.crates/socket-patch-cli/src/commands/vendor.rsrun_vendor_gc(~4202) then lists the entry underkept, disagreeing with the in-use verdict thatvendor --checkuses.
Activity
mikolalysenko commentedon Oct 8, 2026
[agent] Triaged as
priority:p1(npm). This is the re-resolved sibling of the removal cases in #1132 (bun.lockb), #1140 (uv) and #1142 (Pipenv). Those are being fixed in the open PR #1147, which maps a lock entry that left the graph tovendor_lock_entry_removedinstead of drift. #1147 doesn't cover npm, and doesn't cover a live entry whose version changed:npm_lock.rsrevert_one_recordstill warnsvendor_lock_entry_driftedwheneverresolvedleaves the uuid dir, without checking the version. So this needs its own change, ideally reusing the in-use verdict thatvendor --checkalready uses (Discovery::vendor_entry_in_use) so the npm and Bun reverts agree with the check. No open PR addresses it yet; it is eligible to be claimed.Generated by Claude Code
mikolalysenko commentedon Oct 8, 2026
[agent] Claiming this issue (shared root cause: the npm-family vendored reverts treat a lock entry that now resolves a different version as drift instead of "the dependency left the graph"). Branch: agent/fix-vendored-reresolved-version-drift. Claim-ID: 2026-10-08T22:21:20Z-6f5c80
Generated by Claude Code
Revert a vendored npm/Bun package after upgrade
mikolalysenko commentedon Oct 8, 2026
[agent] Draft PR: #1187 (npm and Bun text-lock reverts;
bun.lockbis left for a follow-up after #1147 lands).Generated by Claude Code
Test scan --prune after a vendored npm upgrade
Accept http-to-https registry upgrades
Distrust upgrades a project config can redirect
mikolalysenko commentedon Oct 9, 2026
[agent] Re-check from the npm bug-hunt routine (ledger #302): still reproduces on main
16106b1(Linux, npm 10.9.4), and one more shape for #1187 to cover.When the new version also has a patch (mock offers
ms@2.1.2andms@2.1.3): vendoredms@2.1.2, thennpm install ms@2.1.3, thenscan --mode vendored --prune --yes. The scan vendors and wiresms@2.1.3(npm ciinstalls the patched 2.1.3), but it still printsGC: kept 1 drifted vendored entryfor 2.1.2. Its artifact.socket/vendor/npm/<2.1.2 uuid>/and its ledger entry stay, andvendor --checkkeeps exiting 1 withpkg:npm/ms@2.1.2: dependency removed … run socket-patch scan --mode vendored --prune. So upgrading to a version that has its own patch, which is the normal upgrade path, also loops.Generated by Claude Code
Trust upgrades only without redirecting config
mikolalysenko commentedon Oct 9, 2026
[agent] Bun re-check from the Bun bug-hunt routine (ledger #306), main
793edd4, Linux, real Bun, local mock of the patch API (minimist@1.2.2 + left-pad@1.3.0 vendored, thenbun add minimist@1.2.8, thenscan --mode vendored --prune --yes):vendor --checkbun.lockbGC: reverted 1 vendored entry, minimist uuid dir removedbun.lock(v2)GC: kept 1 drifted vendored entry;vendor --revertalso saysKept 1 drifted package(exit 0)scan --mode vendored --prune")So after #1147 the binary
bun.lockbbackend already handles the re-resolved case (its revert now reads an entry that left the graph as removed), and the "bun.lockb follow-up" that #1187 mentions may not be needed. Bun's text-lock backend still loops, as #1187 expects.Generated by Claude Code
mikolalysenko commentedon Oct 9, 2026
[agent] pnpm re-check from the pnpm bug-hunt routine (ledger #303). The pnpm vendored backends have the same loop, and draft PR #1187 doesn't touch them (it changes
npm_lock.rs/bun_lock.rsonly).Main
f3c6313, Linux, Node 22, real pnpm installs, local mock of the patch API servingleft-pad@1.3.0. Steps: vendorleft-pad@1.3.0(scan --mode vendored),pnpm install, thenpnpm add -E left-pad@1.2.0(-won the 9.x workspace scaffold), thenrm -rf node_modules && pnpm installso #1197's orphaned.pnpmdir can't interfere, then each command in turn:vendor --checkscan --mode vendored --prunevendor --revertremove pkg:npm/left-pad@1.3.0vendor --checkafter allgc.keptVendoredEntriespartialFailure)The ledger entry and
.socket/vendor/npm/<uuid>/stay in every row. A plainscan --mode vendoredkeeps warningvendor_ledger_entry_unwired("runsocket-patch scan --pruneto revert it") on every run.vendor --reverton 12.10.1:The
packages/snapshotsentries are already read as removed. Only the importer dependency (.|left-padnow1.2.0) counts as drift, and that alone keeps the entry. Same for the legacy backend's root dependency.One more side effect: the pruning scan still removes the dead
left-pad@1.3.0override frompackage.json, the lock'soverrides:andpnpm-workspace.yaml(deleting the scaffold file), yet reports the entry as kept. The ledger's recorded wiring then no longer matches the files.Suspect code (the same "version changed means drift" check as
npm_lock.rsrevert_one_record):crates/socket-patch-core/src/vendor/pnpm_lock.rs:3107(importer dep) and:3261(snapshot ref)crates/socket-patch-core/src/vendor/pnpm_lock_legacy.rs:1069(root dep) and:1236(dep ref), for pnpm 7/8A fix for #1155 should cover these too, ideally through the same
vendor_entry_in_useverdict. Not a regression I could bisect (4.0.0 doesn't speak the v5 mock contract). Not probed on macOS/Windows; the logic isn't path-dependent.Generated by Claude Code
mikolalysenko commentedon Oct 9, 2026
v5 release blocker (P1). Normal npm dependency upgrades must not leave vendor --check permanently red with a cleanup command that cannot work. This is ordinary package maintenance, not a stress loop; PR #1187 is pending.
This follows the maintainer's release scope: one normally completing CLI instance, prioritizing valid-lockfile patch/install behavior, compatibility, and actionable CLI UX.
Fix the build after merging main's bun.lockb rework
mikolalysenko commentedon Oct 9, 2026
[agent] The fix PR #1187 is blocked on something outside it. Its own checks pass (build, clippy, unit tests and the other e2e suites). The only red jobs are hosted-e2e, e2e_safety_pnpm and the Bun native compat legs. They fail on every PR because production stopped publishing the free
minimist@1.2.2patch those suites are pinned to (#1293).What a human needs to do:
docs/testing/hosted-production-e2e.md§ "If a required patch is withdrawn". The agent sandbox can't reachpatches-api.socket.dev, so it can't pick a replacement itself.264b6ff, and four commits landed after it.Generated by Claude Code
mikolalysenko commentedon Oct 9, 2026
[agent] The main-wide canary failure that was blocking #1187 is fixed by #1302 (repinned to the republished patch
642d7f02-…). Once #1302 merges, #1187 needs a merge from main, a CI rerun, and a re-approval.