Repository navigation
Root scan --mode vendored exits 1 forever and drops its in-run VEX when a nested independent npm project installs a patched package the root lock doesn't have (hosted mode only warns) #1534
Description
[agent] Found by the scheduled npm bug-hunt routine (ledger #302).
Summary
A repository root is an npm project. tools/x/ is a separate npm project, not a workspace member, with its own package-lock.json and node_modules. It depends on a package that has a patch and that the root doesn't depend on.
socket-patch scan --mode vendored from the root crawls tools/x/node_modules (documented in docs/ecosystems.md, "npm: which node_modules trees are crawled"), downloads the patch, and passes the package to the root package-lock.json vendor backend. The backend refuses with vendor_lock_entry_not_found, so the run ends partialFailure, exit 1. That happens on every run: nothing the user can do at the root clears it.
scan --mode hosted over the same tree skips the package with redirect_unconfirmed / redirect_npm_entry_not_found, and exits 0. The scan code says nested copies are out of scope for both modes: crates/socket-patch-cli/src/commands/scan/mod.rs:1789-1791 reads "Hosted and vendored scans only rewire this root's lockfiles".
The Bun routine found this first on Bun 1.3.x and npm 10 and handed it over (ledger #302 entry 20261014T042736Z-from-bun). This issue is the npm reproduction.
Impact
- CI is permanently red. A root
scan --mode vendoredin any repo with an independent nested npm project (examples/,docs/,tools/) exits 1 every time. The root's own packages are vendored correctly and then reported as already vendored. - No in-run VEX.
scan --mode vendored --vex out.vex.jsonexits 1 and writes no document, even afternpm cihas installed the root's patched bytes. The same shape in hosted mode writes the VEX and exits 0. A standalonesocket-patch vexfrom the root works (exit 0, attests the root's package), so the failure comes only from the scan treating the nested copy as a failure. - The remedy is wrong. It says "make sure the package is installed and locked (
npm install)". The package is installed and locked, intools/x. Runningnpm installat the root or intools/xchanges nothing. - The per-path form fails the same way.
scan --mode vendored . tools/xvendorstools/xcorrectly, but its.leg still crawlstools/x/node_modulesand fails, so the command exits 1. It stays red even aftertools/xis vendored and reinstalled with patched bytes.
Repro
A local mock of the org patch API serves free patches for left-pad@1.3.0 and minimist@1.2.5. It answers batch, by-package, view and patches/package with a real patched tarball. Every socket-patch command gets --api-url <mock> --api-token fake --org test-org.
mkdir -p r/tools/x && cd r && git init -q && echo node_modules/ > .gitignore
echo '{"name":"root","private":true,"dependencies":{"minimist":"1.2.5"}}' > package.json
echo '{"name":"x","private":true,"dependencies":{"left-pad":"1.3.0"}}' > tools/x/package.json
npm install && (cd tools/x && npm install)
socket-patch scan --mode vendored --json --yes # run 1: partialFailure, exit 1
socket-patch scan --mode vendored --json --yes # run 2: partialFailure, exit 1 (minimist already_vendored)
npm ci
socket-patch scan --mode vendored --yes --vex out.vex.json --vex-product pkg:npm/root@1.0.0 # exit 1, no out.vex.json
socket-patch vex -O out.vex.json --product pkg:npm/root@1.0.0 --offline # exit 0, attests minimist
# hosted control (fresh copy of the same tree)
socket-patch scan --mode hosted --json --yes --patch-server-url <mock> # success, exit 0Vendored output (npm 10.9.4, main 37c8918):
status: partialFailure (exit 1)
downloaded pkg:npm/left-pad@1.3.0
downloaded pkg:npm/minimist@1.2.5
failed pkg:npm/left-pad@1.3.0 vendor_lock_entry_not_found package-lock.json has no rewritable entry for left-pad@1.3.0 — make sure the package is installed and locked (`npm install`) before vendoring
applied pkg:npm/minimist@1.2.5
Hosted output for the same tree:
status: success (exit 0)
skipped pkg:npm/left-pad@1.3.0 redirect_unconfirmed no lockfile entry pinning it could be rewritten
applied pkg:npm/minimist@1.2.5
W redirect_npm_entry_not_found: no package-lock.json entry for left-pad@1.3.0
Expected vs actual
- Expected. A hosted or vendored scan rewires only the scan root's lockfiles (scan/mod.rs:1789). A nested project is reached by passing its PATH (docs/configuration.md: "Each PATH in hosted or vendored mode is a project directory"). So a crawled copy that belongs to a nested project should get the same treatment as in hosted mode: a warning that names the nested project and the PATH to run, with no failed event and no effect on the exit code or the in-run VEX. CLI_CONTRACT.md's exit-code rules reserve
partialFailurefor packages the run should have patched and couldn't. - Actual. The run fails with exit 1 on every run, omits the in-run VEX, and gives a remedy (
npm install) that can't clear it.scan . tools/xis also red.
Matrix
| OS | npm / Node | socket-patch | Reproduces |
|---|---|---|---|
| Linux | 10.9.4 / Node 22.22 | main 37c8918 |
yes (×3, incl. --vex and . tools/x) |
| Linux | 12.2.0 / Node 22.22 | main 37c8918 |
yes (×2 runs) |
| Linux | 8.19.4 / Node 22.22 | main 37c8918 |
yes (×2 runs) |
| Linux | 10.9.4 / Node 22.22 | v5.0.1 (npm latest) |
yes |
| Linux | Bun 1.3.0 / 1.3.9 / 1.4.3 | bb73e2f |
yes (Bun routine) |
| macOS / Windows | — | — | untested (probe branches unavailable). The logic is OS-independent. |
First bad: pre-existing. It is in v5.0.1 already.
Suspect code
crates/socket-patch-cli/src/commands/scan/mod.rs:1788-1796: agent and report-only scans calljudge_nested_rootsto assign each crawled copy to its own project root (Agent-mode scan ignores socket.yml includePaths / ignorePaths (and the built-in tests/ default) for nested npm projects, patching every nested project's node_modules #554). Hosted and vendored scans skip it, so vendored hands every crawled purl to the root backend.crates/socket-patch-core/src/vendor/npm_lock.rs:534-540: an absent entry becomes the hardvendor_lock_entry_not_foundrefusal, where hosted's equivalent (redirect_npm_entry_not_found) is a warning plus a skip.
🤖 Generated with Claude Code
Activity
mikolalysenko commentedon Oct 14, 2026
[agent] Triage:
priority:p1(npm; the Bun report the bug-hunt routine handed over goes through the same scan path). Confirmed on maina3f56f7: incrates/socket-patch-cli/src/commands/scan/mod.rs,policy.judge_nested_roots(...)runs only when the mode is neither hosted nor vendored, so a vendored root scan hands every crawled copy, including ones under a nested project'snode_modules, to the root lock backend. The npm backend then turns the missing root entry into the hardvendor_lock_entry_not_foundrefusal (crates/socket-patch-core/src/vendor/npm_lock.rs), where hosted skips with a warning.Not a duplicate: #554 fixed the same nesting question for agent and report-only scans only. No open PR covers it. Unclustered, cluster of one.
Generated by Claude Code
mikolalysenko commentedon Oct 14, 2026
[agent] Claiming this issue (shared root cause: hosted/vendored scans hand crawled copies from nested independent npm projects to the root lock backend). Branch: agent/fix-vendored-nested-project-copies. Claim-ID: 2026-10-14T18:21:08Z-f2ff7c
Generated by Claude Code
mikolalysenko commentedon Oct 14, 2026
[agent] Draft PR: #1543
Generated by Claude Code
Start fix for #1534
Test vendored scans over nested npm projects
Leave nested npm projects' copies to their run