Repository navigation
Vendored scan/get run from an npm workspace member directory refuses with "no package-lock.json … run your package manager's install first", a remedy that loops; hosted names the workspace root #1904
Description
[agent] Found by the scheduled npm bug-hunt routine (ledger #302).
Summary
When socket-patch scan --mode vendored or get <uuid> --mode vendored runs from an npm workspace member directory (packages/a) that has no lock of its own, it refuses with vendor_lockfile_missing. That's correct: npm installs the member from the root package-lock.json. But the refusal says:
no package-lock.json, npm-shrinkwrap.json, yarn.lock, pnpm-lock.yaml, bun.lock, bun.lockb, or vlt-lock.json in the project root — vendoring rewires the lockfile, so one must exist (run your package manager's install first)
Following that advice doesn't help. Running npm install in the member updates the root lock, and the next vendored run prints the same refusal. The message never says the directory is a workspace member or which directory to run from.
The other refusals for the same layout do name the root:
- Hosted (the default mode):
redirect_workspace_lockfile_elsewhere, ".is a workspace member with no lockfile of its own … run socket-patch from<root>(the workspace root); nothing was written". - Vendored, when the member has a stray
package-lock.jsonof its own (Hosted and vendored scans run from an npm workspace member that has a stray package-lock.json of its own rewrite that lock, which npm ignores, and report success while npm installs the unpatched package and VEX attests not_affected #1094,member_stray_lock_refusal): "… vendor from<root>(the workspace root)".
So the common case (a member with no lock) gets the least helpful message. docs/ecosystems.md (vlt workspace members) describes these refusals as redirect_workspace_lockfile_elsewhere / vendor_lockfile_missing "and names the root to run from".
Impact
The run fails closed (exit 1, nothing is written), so this is a diagnostics bug. A user who runs socket-patch from a member directory, which is common in monorepos, gets advice that loops and no pointer to the workspace root. The --json output has the same text under errorCode: vendor_lockfile_missing, so tooling can't tell this case apart from a project that really has no lock.
Repro (Linux, Node 22, npm 8.19.4 / 10.9.4 / 12.2.1)
mkdir -p ws/packages/a && cd ws
echo '{"name":"app","version":"1.0.0","private":true,"workspaces":["packages/*"],"dependencies":{"plainpkg":"2.0.0"}}' > package.json
echo '{"name":"a","version":"1.0.0","dependencies":{"plainpkg":"1.0.0"}}' > packages/a/package.json
npm install # root package-lock.json; plainpkg@1.0.0 lands in packages/a/node_modules
cd packages/a
socket-patch scan --mode vendored # exit 1: "no package-lock.json … in the project root … run your package manager's install first"
npm install # follow the remedy: updates ../../package-lock.json
socket-patch scan --mode vendored # same refusal again
socket-patch scan # hosted: redirect_workspace_lockfile_elsewhere, names the workspace rootplainpkg@1.0.0 stands for any package with an available patch. I used a local mock of the patch API and registry (SOCKET_PROXY_URL, SOCKET_PATCH_SERVER_URL, npm_config_registry). The refusal fires before any patch-specific work, so any patched package shows it.
Expected vs actual
- Expected: the vendored refusal names the workspace root, as hosted and the stray-lock vendored refusal do. For example: "
.is a workspace member; npm installs it from<root>/package-lock.json; vendor from<root>(the workspace root)". The code can stayvendor_lockfile_missing. - Actual: a generic "no lockfile, run install first" message, whose remedy loops.
OS × version
| OS | npm | main cbbbdb9 |
v5.0.1 |
|---|---|---|---|
| Linux | 8.19.4 | reproduces | — |
| Linux | 10.9.4 | reproduces | reproduces |
| Linux | 12.2.1 | reproduces | — |
macOS and Windows weren't probed (probe branches are blocked for this routine). The code path doesn't depend on the OS.
This isn't a regression: v5.0.1 behaves the same.
Suspect code
crates/socket-patch-core/src/vendor/npm_flavor.rs:250-262: step 4 ("Nothing recognizable") of the lock-flavor detection always returns the generic message. It doesn't consulthosted::governing_root(the package.jsonworkspacesancestor check that hosted'srefusaluses).crates/socket-patch-core/src/vendor/npm_flavor.rs:403-424(member_stray_lock_refusal) builds the root-naming detail, but only when the member has a stray lock.- The same step-4 path serves yarn / Bun members, so those are probably affected too. I only tested npm.
Activity
mikolalysenko commentedon Nov 1, 2026
[agent] Triage: priority:p1 (npm-family). Confirmed by code on main cbbbdb9: step 4 of the lock-flavor detection in
vendor/npm_flavor.rsreturns the genericvendor_lockfile_missingtext without thehosted::governing_rootancestor check that hosted's refusal andmember_stray_lock_refusaluse. No duplicate or open PR found. This doesn't share a root cause with #1900 (that one is about howworkspacesis parsed, not about which refusal is shown).Generated by Claude Code
mikolalysenko commentedon Nov 1, 2026
[agent] Claiming this issue (shared root cause: vendored lock-flavor detection's no-lock step never checks for a governing workspace root). Branch: agent/fix-vendored-member-lockfile-refusal. Claim-ID: 2026-11-01T04:20:58Z-36493f
Generated by Claude Code
mikolalysenko commentedon Nov 1, 2026
[agent] Fix in progress: #1913
Generated by Claude Code
Name the workspace root in vendored no-lock refusal