Fix agent mode skipping npm-aliased copies (#356) - #738
Open
Mikola Lysenko (mikolalysenko) wants to merge 4 commits into
Open
Mikola Lysenko (mikolalysenko) wants to merge 4 commits into
Mikola Lysenko (mikolalysenko) wants to merge 4 commits into
Conversation
Assisted-by: Claude Code:claude-opus-5-5
An npm alias such as "lp": "npm:left-pad@1.3.0" installs the real
left-pad@1.3.0 at node_modules/lp. Agent mode only looked for the
package at node_modules/left-pad, so:
- an alias-only project got package_not_installed from apply;
- a project with a plain copy and an alias patched only the plain
copy, reported success, and vex attested not_affected while
require('lp') still loaded the unpatched file.
The resolver now also treats a real package dir whose own
package.json names the patched name@version as a copy of it, under
any dir name (plain or scoped). Links still never count, so pnpm's
isolated layout and workspace links are unchanged. apply, rollback
and vex all share this resolver, so all three now cover alias copies
under npm, yarn, Bun and pnpm's hoisted linker.
Fixes #356
Assisted-by: Claude Code:claude-opus-5-5
Assisted-by: Claude Code:claude-opus-5-5
The installed-tree lookup now returns npm alias installs itself, so hosted VEX's own alias walk mostly finds paths that lookup already returned. Merge them without duplicates. The dispatcher test that pinned the old "alias is missing" behavior now expects the resolver to find node_modules/lp directly. Refs #356 Assisted-by: Claude Code:claude-opus-5-5
Mikola Lysenko (mikolalysenko)
marked this pull request as ready for review
October 4, 2026 02:19
Collaborator
Author
|
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 07824bd. Configure here.
Collaborator
Author
|
Ready for review — head
Generated by Claude Code |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
LLM Description written by Claude Code:claude-opus-5-5
Fixes #356
Summary
Agent mode now treats an npm alias install (
"lp": "npm:left-pad@1.3.0", which puts the realleft-pad@1.3.0atnode_modules/lp) as an installed copy ofpkg:npm/left-pad@1.3.0.apply,rollbackandvexall go through the same resolver, so all three now cover alias copies under npm, yarn, Bun and pnpm's hoisted linker.Before: an alias-only project got
package_not_installedfromapply. A project with a plain copy plus an alias got only the plain copy patched,success, and a VEXnot_affectedstatement whilerequire('lp')loaded unpatched code.Root cause
NpmCrawler::find_by_purls(crates/socket-patch-core/src/crawlers/npm_crawler.rs) only probes<node_modules>/<purl-name>, andvisit_resolver_dirrequires the dir name to equal thepackage.jsonname. An alias dir's name never equals its package's name, so it was never a candidate.Change
visit_resolver_dir(importer-tree visits only) addsalias_copies: real package dirs, plain or under a@scope, whose ownpackage.jsonname@versionis a pending target while the dir is named otherwise. It also checks nestednode_modules, because the BFS visits them.npm linktarget, so pnpm's isolated layout and Fix agent mode patching linked first-party source (#626) #634's first-party-link handling are unchanged. A dir whose name is its own package name (in any ASCII case) is left to the direct probe, so one physical dir is never recorded twice on case-insensitive filesystems.get,vendor) still pick it.npm_crawler/oracle.rs) mirrors the rule, so the randomized tree tests keep comparing like with like. Their generator already produces alias installs.vex_consumed::npm_alias_copies) now mostly re-finds paths the installed-tree lookup already returns, so its results are merged without duplicates.Wrappers (
npm/,pypi/,gem/) only dispatch to the binary, so they need no change.Test evidence
Red→green (each new test was run with the
alias_copiescall disabled, then enabled):package_not_installed)npm_crawler::tests::find_by_purls_resolves_an_alias_only_installnpm_crawler::tests::find_by_purls_returns_alias_copies_beside_the_plain_copynpm_crawler::tests::find_by_purls_resolves_scoped_alias_installs--vex)e2e_embedded_vex::apply_vex_patches_npm_alias_copiesvexrefuses while an alias copy is unpatchede2e_vex::verify_mode_requires_npm_alias_copies_patchednpm_crawler::tests::find_by_purls_does_not_take_a_link_as_an_alias_copyReal toolchains (Linux, hand-staged manifest + blobs,
apply --offline --vex):left-pad+lp+@x/pad: applied 3, every copy patched,require('lp')loads patched bytes, VEXnot_affected.rollback --offlinerestores all three. With onlynode_modules/left-padpatched,vexexits 1:omitting pkg:npm/left-pad@1.3.0 … (not_applied).node-linker=hoisted(lp+left-pad): applied 2, both patched.lp+left-pad): applied 2, both patched.Local checks:
cargo clippy --workspace --all-features -- -D warnings: clean.cargo fmt: changed hunks are formatted.mainitself is not fmt-clean (498 diffs), so I only formatted my own hunks.cargo test --workspace --all-features: 213 suites ok. The 12 failures are all permission/write-failure tests (chmod 0555-based, e.g.covgap_commands_vendor::*_state_write_failure_*,vlt_healunremovable-lock,copy_treerelax loop) that cannot fail as root in this sandbox. They don't touch the resolver, and CI runs them as non-root. The 13th,ecosystem_dispatch::tests::npm_crawl_snapshot_matches_the_crawls_it_replaces, pinned the old "alias is missing" behavior and is updated in 07824bd.node --test npm/socket-patch/bin/socket-patch.test.mjs: 4/4.Follow-ups (not in this PR)
vex_consumed::npm_alias_copies(the hosted-VEX alias walk) is now largely redundant with the resolver and could be deleted in favour of it. Left in place to keep this change small.dependenciesmirror for aliases) is a different code path (the hosted lock rewriter), not this resolver.Note
Medium Risk
Changes core npm package discovery used by apply, rollback, and VEX; incorrect alias/link handling could patch wrong dirs or miss copies, but behavior is heavily tested and scoped to importer-tree alias detection.
Overview
Fixes #356 by teaching the npm installed-tree resolver to treat npm alias installs (e.g.
"lp": "npm:left-pad@1.3.0"→ real package atnode_modules/lp) as additional copies of the target PURL, alongside plainnode_modules/<name>paths.NpmCrawler::find_by_purls/visit_resolver_dirnow runsalias_copieson importer-treenode_modules: it scans real package dirs (including scoped layouts) whosepackage.jsonname@versionmatches a pending target while the directory name differs, skips symlinks and dirs that already match their package name (avoids double-counting), and keeps plain copies first in the result order. The async oracle mirrors the same rule for randomized equivalence tests.apply,rollback, andvexall use this resolver, so alias-only trees no longer getpackage_not_installed, mixed plain+alias trees get every copy patched/verified, and VEX no longer attestsnot_affectedwhilerequire('lp')still loads pristine bytes. Hosted VEX path merging dedupes alias walk results against paths the resolver already returned. CLI_CONTRACT documents alias installs as supported copies.Coverage adds unit tests for alias-only, plain+alias+nested, scoped aliases, and “links are not alias copies”, plus e2e tests for
apply --vexandvexverify mode; snapshot/dispatch tests expectleft-padresolved vianode_modules/lp.Reviewed by Cursor Bugbot for commit 07824bd. Configure here.
Generated by Claude Code