Repository navigation
Fix lockless nested npm projects in root scans (#1554) - #1591
Conversation
Assisted-by: Claude Code:claude-opus-5-5
A root `scan --mode vendored` over a repo that holds an npm project installed without a lockfile (`package-lock=false` in its .npmrc, or `npm install --no-package-lock`) handed that project's packages to the root's lockfile. The root lock never lists them, so every run failed with vendor_lock_entry_not_found, exited 1 and dropped the in-run VEX, and the suggested `npm install` could not clear it. Such a directory, with its own package.json and node_modules and not listed by any workspace, is now treated like a nested project with its own lock: its packages get the nested_project_skipped warning naming the PATH to scan, and the root run succeeds. A member of a lockless inner workspace counts as part of that workspace's project. Fixes #1554 Assisted-by: Claude Code:claude-opus-5-5
The nested_project_skipped warning for a project installed without a lockfile said it had its own lockfile and told the user to run a hosted or vendored scan of it, which is refused there for lack of a lock. It now says the project has no lockfile and offers two remedies that work: create the lock with `npm install --package-lock` and scan it, or patch its node_modules in place with agent mode. The lockless check also stops at the scan root, so a workspace file above the directory being scanned no longer decides what counts as nested below it; a pnpm workspace root no longer claims a directory it does not list; and Rush projects stay the Rush root's. Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
A pnpm-workspace.yaml whose packages: globs use braces or classes
(`{apps,libs}/*`), or that could not be read, made every lockless
project below it count as a workspace member, so a root vendored scan
still failed on it. Such globs are now matched with the shared
workspace glob grammar, and an unreadable file lists nothing, since
pnpm cannot install from it either.
Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
The brace-glob fallback for pnpm-workspace.yaml matched a directory if any package manager's reading did, so a `!` exclusion, a `test` or `tests` directory, or a dot directory could still count as a member, and a lockless project there failed the root vendored scan. The fallback now reads each glob pnpm's way. Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
The brace-glob fallback stripped every leading `!`, so `!!apps/x` excluded apps/x, while pnpm's own matcher strips one `!` and treats the rest as a literal that matches nothing. It now strips one. Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
|
Ready for review — head
Slack announcement not sent this run (send unavailable); the next run retries. Generated by Claude Code |
The nightly e2e_vex_build pipenv legs (ubuntu and macOS) went red on 2026-10-20: `uv pip install -r pylock.toml` refuses the pylock Pipenv 2026.8.0 writes with "The target environment is not supported by the lockfile". Pipenv emits `environments = []`, and uv 0.13.2 (released 2026-10-19) started treating an empty list as "no environment supported". setup-uv installs the latest uv when no version is given, so the oracle drifted under an unchanged main. Pin uv to 0.13.1, the last version these legs passed on, in ci.yml and pipenv-compatibility.yml, as the other workflows already pin their uv. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01H4ww7ytRoApPcBHiJN4bm2 (cherry picked from commit a7c9207)
|
[agent] The merge queue removed this PR because of This PR did not cause it. The pipenv legs install whatever uv is latest, which was 0.13.1 in yesterday's passing queue run and 0.13.4 in today's. uv ≥ 0.13.2 refuses the e7987ac merges Generated by Claude Code |
|
Wenxin Jiang (@Wenxin-Jiang) this changed after your approval (on Besides merges from main, the only new commit is e7987ac: the CI-only uv pin from #1639, byte-for-byte the same 12 added lines ( If that's fine with you, re-approving Generated by Claude Code |
|
bugbot run Generated by Claude Code |
|
[agent] The merge queue removed this PR again, this time for This PR did not cause it:
Every test that sets No fix exists yet. A proposed patch for a separate PR is to let the re-acquire in that test retry briefly: let reacquired = (0..50).find_map(|_| match acquire_update_lock() {
Err(UpdateError::InProgress) => { std::thread::sleep(std::time::Duration::from_millis(20)); None }
other => Some(other),
});
assert!(matches!(reacquired, Some(Ok(Some(_)))), "lock must be reacquirable after release");I am putting this PR back in the queue once. If it fails a second time, I will treat that as a real failure. Generated by Claude Code |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit e7987ac. Configure here.
LLM Description written by Claude Code:claude-opus-5-5
Fixes #1554
Summary
A root
scan --mode vendoredover a repo that holds an npm project installed without a lockfile (package-lock=falsein its.npmrc, ornpm install --no-package-lock) failed every run withvendor_lock_entry_not_found, exited 1 and dropped the in-run VEX. The suggestednpm installcouldn't clear it. Such a project is now treated like a nested project that has its own lock (#1534/#1543): its packages get anested_project_skippedwarning, and the root run succeeds and writes its VEX.Root cause
nested_npm_project(crates/socket-patch-core/src/hosted/governing_root.rs) only counted a directory below the scan root as an independent project if it held its own npm-family lock. A lockless project was never recognised, so its copies stayed with the root's vendor backend, and the root lock never lists them.Fix
nested_npm_projectchecks lock roots first, as before. If none matches, it falls back tolockless_npm_project. That function takes the directory that owns the copy's firstnode_modulesand, as long as a workspace lists it, climbs to that workspace's root. The result counts as independent when it sits strictly below the scan root and has apackage.json.listing_workspacedecides membership whether or not the workspace was ever locked. It readspackage.json/vlt.jsonworkspacesin every package manager's dialect, pluspnpm-workspace.yamlpackages:vialists_as_member, which leans toward "member" when it can't tell. It also treats a Rush root as an owner. The walk stops at the scan root.nested_project_skippedwarning for a lockless project now says the project has no lockfile. It offers remedies that work: runnpm install --package-lockthere and then scan it, orsocket-patch scan --mode agent <dir>. The old text claimed the project had its own lockfile and pointed to a hosted/vendored rerun, which would be refused.docs/ecosystems.mdis updated to match.Tests (red → green)
governing_root::tests::nested_npm_project_finds_a_lockless_projectleft: None, right: Some(".../tools/x")governing_root::tests::lockless_project_under_other_roots.npmrc package-lock=falseand plain lockless; 2 runs +--vex)scan::scan_nested_project_e2e::root_vendored_scan_skips_a_lockless_nested_projectnpmrc=true run 1: exit 0assertion fails (exit 1)Commands run locally:
cargo fmt --all -- --check: cleancargo clippy --workspace --all-features -- -D warnings: cleancargo test -p socket-patch-core --lib governing_root: 45 passedcargo test -p socket-patch-cli --all-features --test scan scan_nested_project: 7 passedcargo test -p socket-patch-cli --all-features --test remedy_commands_parse: 2 passed (the new remedy commands parse)cargo test --workspace --all-features --no-fail-fast: all pass except 10 tests that rely on a write or remove being denied (*_write_failure_*,*_unremovable_*,repair_cleanup_failure_*,relax_loop_must_not_traverse_symlinked_root,e2e_bun_lockb::binary_shared_bundled_record_hosted_pin_is_managed). The sandbox runs as uid 0, so those denials never happen there. None of them reachesnested_npm_projector the scan nested-project path. CI runs as non-root and is the check for them.Bugbot
!,testand dot rules). Fixed in 121882e with a test; the thread is resolved. CI on 80868e4: 34 pass, 0 fail.!was stripped). Fixed in 6f4c327; the thread is resolved./code-review high
I fixed every confirmed finding except two:
tools/xlocked,tools/x/sublockless): not changed. The existing Rootscan --mode vendoredexits 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 test pins this ("a lockless directory belongs to the nearest lock root"). In either case the copy is skipped from the root run. Only the directory the warning names differs.package_json_workspace_root/vlt_workspace_root: not done. Those walks canonicalize and weigh locks, while this one is deliberately lock-agnostic and bounded at the scan root. Merging them would widen this PR into a refactor (see Tracking: derive scan candidates, lock entries and hosted refs from one project inventory instead of four discovery systems #1112).file:link targets: the root lock lists their copies, so they are alreadylockedand never reach this check.🤖 Generated with Claude Code
https://claude.ai/code/session_01LYN2ka9heR9fMVjsgTJGfZ
Generated by Claude Code
Note
Medium Risk
Changes nested-project detection and skip/warning paths for hosted/vendored npm scans; incorrect workspace or lockless classification could skip or patch the wrong packages, though coverage is broad.
Overview
Root vendored/hosted scans no longer fail with
vendor_lock_entry_not_foundwhen a nested directory haspackage.jsonandnode_modulesbut no lockfile (package-lock=falseornpm install --no-package-lock). Those installs are skipped like nested projects with their own lock (#1534).nested_npm_projectingoverning_root.rsstill prefers directories with an npm-family lock (or Rush). When none match, it uses newlockless_npm_projectlogic: walk from the copy’s firstnode_modulesup to the scan root, climbing to a workspace root whenlisting_workspacelists the path (npm/yarn/vlt/pnpm/Rush, including partial pnpm glob handling).has_own_npm_family_lockis exported for discovery warnings.nested_project_skippedtext now splits locked vs lockless nested projects: lockless warnings suggestnpm install --package-lockthen a targeted scan, orsocket-patch scan --mode agent <dir>. Docs inecosystems.mdmatch.CI pins uv 0.13.1 in
ci.ymlandpipenv-compatibility.ymlbecause 0.13.2 rejects Pipenv pylocks withenvironments = [].Unit tests in
governing_rootand e2eroot_vendored_scan_skips_a_lockless_nested_projectcover the behavior.Reviewed by Cursor Bugbot for commit e7987ac. Configure here.