Repository navigation
Fix path-scoped scan missing member alias links (#1266) - #1470
Mikola Lysenko (mikolalysenko) wants to merge 4 commits into
Conversation
Assisted-by: Claude Code:claude-opus-5-5
Under pnpm's default isolated linker and npm's linked install
strategy, a workspace member's alias dependency ("lp":
"npm:left-pad@1.3.0") is a symlink from packages/a/node_modules/lp
into the root store. `scan --mode agent packages/a` never selected it
and `rollback packages/a` never restored it, both reporting success,
while the member's plain-name links were handled.
Path scoping now also counts every package link in a node_modules
root that resolves to one of a package's installed copies. The
crawler itself still records only physical copies, so apply and VEX
see each copy once, as before.
Fixes #1266
Assisted-by: Claude Code:claude-opus-5-5
ac8b66c to
88c91ff
Compare
The link pass lived in the resolver rollback also restores from, so every rollback did extra filesystem work it then threw away. It now runs only for scan and rollback path targets, in its own lookup. A member's alias link can also point at a pnpm peer variant the crawl skipped once it found the root's copy, so store peer variants now count when matching links. A failed link enumeration falls back to the plain copies instead of aborting the run. Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
1 similar comment
|
BugBot review Generated by Claude Code |
|
Labeled Ready for review by the burn-down agent.
Generated by Claude Code |
Final review: blocking perf regression on path-scoped
|
| Store entries | merge-base | this head |
|---|---|---|
| 1,000 | 0.10 s | 6.9 s |
| 3,000 | 0.24 s | 53 s |
Unscoped scan on this head stays at 0.16 s for N=3,000. Release builds are faster but still quadratic, so a store of about 5,000 entries would take minutes.
Suggested fix (any one)
- Enumerate the
node_modulesroot links once, canonicalize each, read the target'spackage.jsonname@version, and match it against the unresolved purls. This avoids per-purl variant expansion. - Or expand variants only for purls with a link target that matched no known copy.
- Or share one
scannedset and one store listing across all purls. - A large-store regression test or bench would keep this from coming back.
Non-blocking
- Root
node_moduleslinks now select too (e.g.node_modules/left-pad). That fits the semantics, but theCLI_CONTRACT.mdclause only describes member aliases. - The new tests are all
#[cfg(unix)]. find_all_packages_for_rollback_reusingmay have no non-test caller left.
Question for a human: is the scoped-scan slowdown acceptable to ship, or should this wait for one of the fixes above? I recommend the fix. I've labelled this agent:needs-human and removed Ready for review; whoever picks it up can drop the label once it's fixed.
Generated by Claude Code
The previous commit matched member links by expanding every crawled npm purl's store peer variants, re-listing the whole store once per purl. A path-scoped scan of a 3,000-entry pnpm store went from 0.24s to 35s (debug build). Links in each node_modules root are now matched by their target's own package.json name@version, in one pass however many purls are asked for. The same 3,000-entry scan takes 0.24s again, and a link to a peer variant the crawl skipped still counts. Assisted-by: Claude Code:claude-opus-5-5
|
[agent] Thanks, the regression was real. Fixed in 4288a72.
Both select the same single package. 4288a72 matches your merge-base numbers.
I'm removing Generated by Claude Code |
|
BugBot review 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 4288a72. Configure here.
|
Labeled Ready for review by the burn-down agent.
Generated by Claude Code |
Final review briefWhat it does: Path-scoped Risk: low. It only adds match candidates for path-scoped runs. Writes still go through the crawled copies, so it can't touch anything an unscoped run wouldn't. Links are followed read-only ( Look here
Verified
Changes I made: none. Open questions (non-blocking)
Auto-merge is armed, so approving sends this straight to the merge queue. Generated by Claude Code |
LLM Description written by Claude Code:claude-opus-5-5
Fixes #1266
What users saw
In a workspace using pnpm's default isolated linker, or npm with
install-strategy=linked, a member that depends on an alias(
"lp": "npm:left-pad@1.3.0") getspackages/a/node_modules/lpas asymlink into the root store:
scan --mode agent packages/anever selected left-pad, so the copymember a loads stayed unpatched. It reported
successand exited 0.rollback packages/aleft the patched bytes in place (npm:success;pnpm: exit 1
path_glob_no_match).The member's plain-name links (
packages/a/node_modules/ms) worked.Root cause
Path-scoped
scanandrollbackmatch patterns against the pathsfind_all_packages_for_rollback_reusingreturns, which are the npmcrawler's copies. The crawler records a plain-name link at the member
path, but never takes an alias link as a copy. That rule is
deliberate (#856): apply and VEX must see each physical copy once, and
the
vex_consumedtests pin it. Path scoping asks a different question:which install paths reach the package.
Fix
A new `find_package_paths_for_path_scope` (used only by `scan`'s and
`rollback`'s path targets) returns the physical copies plus every
package link directly in a `node_modules` root (scoped ones one level
down) whose target's own `package.json` name@version is one of the
requested npm purls. Links are matched by identity in one pass over the
root links, so a member alias pointing at a store peer variant the crawl
skipped still counts. Apply, VEX and rollback's restore list are
unchanged. `CLI_CONTRACT.md` ("Path-scoped scans") now describes this.
A first attempt changed the crawler instead. It broke the #856
`vex_consumed` invariants, so I dropped it. A second version
(84b3690) expanded store peer variants once per purl. Review measured
it as quadratic on big pnpm stores, and 4288a72 replaces it:
Tests (red on main, green here)
scan::scan_paths_e2e::paths_scope_selects_pnpm_member_alias_linkscan::scan_paths_e2e::paths_scope_selects_npm_linked_member_alias_linkscan::scan_paths_e2e::paths_scope_selects_member_alias_link_to_a_peer_variantrollback packages/a(pnpm.pnpmand npm.store)rollback::rollback_duality_invariants::path_scoped_rollback_selects_member_alias_linkWithout the fix, the scan tests fail with a batch body of only
{"components":[{"purl":"pkg:npm/ms@2.1.2"}]}, and the rollback testfails on the exit code. The peer-variant test also fails if the
variant expansion is removed.
Local runs:
cargo fmt --all -- --check: cleancargo clippy --workspace --all-features -- -D warnings: cleancargo test --workspace --all-features --no-fail-fast: everythingpasses except 13 tests that also can't pass in this sandbox on
main. Most are write-failure tests that need chmod-denied writes,and the sandbox runs as root. One bun lockb test needs network access
to patches-api.socket.dev. None of them touch path scoping, and the
core crate is unchanged.
/code-review highfound 6 issues and all of them are fixed in84b3690: the peer-variant gap, moving the link pass out of rollback's
restore resolver, skipping the root clone when there are no npm purls,
no panic on a join error,
Ecosystem::from_purlin place of a prefixtest, and folding a duplicate test scan into the loop.
🤖 Generated with Claude Code
https://claude.ai/code/session_011KEL5sGU2JuwHvDCpJtMwi
Generated by Claude Code