Skip to content

Fix agent mode skipping npm-aliased copies (#356) - #738

Open
Mikola Lysenko (mikolalysenko) wants to merge 4 commits into
mainfrom
agent/fix-npm-agent-alias-copies
Open

Mikola Lysenko (mikolalysenko) wants to merge 4 commits into
mainfrom
agent/fix-npm-agent-alias-copies

Conversation

@mikolalysenko

@mikolalysenko Mikola Lysenko (mikolalysenko) commented Oct 4, 2026 •

Copy link
Copy Markdown
Collaborator

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 real left-pad@1.3.0 at node_modules/lp) as an installed copy of pkg:npm/left-pad@1.3.0. apply, rollback and vex all 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_installed from apply. A project with a plain copy plus an alias got only the plain copy patched, success, and a VEX not_affected statement while require('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>, and visit_resolver_dir requires the dir name to equal the package.json name. 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) adds alias_copies: real package dirs, plain or under a @scope, whose own package.json name@version is a pending target while the dir is named otherwise. It also checks nested node_modules, because the BFS visits them.
  • Links never count. A link is a dependency edge into a store, a workspace member or an npm link target, 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.
  • The plain copy stays first (root-copy-first order), so single-representative consumers (get, vendor) still pick it.
  • The sequential equivalence oracle (npm_crawler/oracle.rs) mirrors the rule, so the randomized tree tests keep comparing like with like. Their generator already produces alias installs.
  • Hosted VEX's separate alias walk (vex_consumed::npm_alias_copies) now mostly re-finds paths the installed-tree lookup already returns, so its results are merged without duplicates.
  • CLI_CONTRACT documents alias installs as copies.

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_copies call disabled, then enabled):

Issue variant Test Without fix With fix
#356 alias-only (package_not_installed) npm_crawler::tests::find_by_purls_resolves_an_alias_only_install FAIL pass
#356 plain + alias, nested alias npm_crawler::tests::find_by_purls_returns_alias_copies_beside_the_plain_copy FAIL pass
#356 scoped alias / alias of a scoped pkg npm_crawler::tests::find_by_purls_resolves_scoped_alias_installs FAIL pass
#356 apply patches every alias copy (in-run --vex) e2e_embedded_vex::apply_vex_patches_npm_alias_copies FAIL pass
#356 vex refuses while an alias copy is unpatched e2e_vex::verify_mode_requires_npm_alias_copies_patched FAIL pass
links are not alias copies npm_crawler::tests::find_by_purls_does_not_take_a_link_as_an_alias_copy pass pass

Real toolchains (Linux, hand-staged manifest + blobs, apply --offline --vex):

  • npm 10.9 with left-pad + lp + @x/pad: applied 3, every copy patched, require('lp') loads patched bytes, VEX not_affected. rollback --offline restores all three. With only node_modules/left-pad patched, vex exits 1: omitting pkg:npm/left-pad@1.3.0 … (not_applied).
  • pnpm 10.28 node-linker=hoisted (lp + left-pad): applied 2, both patched.
  • Bun 1.3.14 (lp + left-pad): applied 2, both patched.

Local checks:

  • cargo clippy --workspace --all-features -- -D warnings: clean.
  • cargo fmt: changed hunks are formatted. main itself 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_heal unremovable-lock, copy_tree relax 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)


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 at node_modules/lp) as additional copies of the target PURL, alongside plain node_modules/<name> paths.

NpmCrawler::find_by_purls / visit_resolver_dir now runs alias_copies on importer-tree node_modules: it scans real package dirs (including scoped layouts) whose package.json name@version matches 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, and vex all use this resolver, so alias-only trees no longer get package_not_installed, mixed plain+alias trees get every copy patched/verified, and VEX no longer attests not_affected while require('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 --vex and vex verify mode; snapshot/dispatch tests expect left-pad resolved via node_modules/lp.

Reviewed by Cursor Bugbot for commit 07824bd. Configure here.


Generated by Claude Code

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
@mikolalysenko
Mikola Lysenko (mikolalysenko) marked this pull request as ready for review October 4, 2026 02:19
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

BugBot review


Generated by Claude Code

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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.

@mikolalysenko Mikola Lysenko (mikolalysenko) added the Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review label Oct 4, 2026
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Ready for review — head 07824bd00d83a1a2eb3dfb2bf7a8fc2a0941a5f7

  • CI: all check runs green on this head (0 failing, none pending; skipped matrix legs only)
  • Mergeable: yes, no conflicts with main
  • Bugbot: reviewed 07824bd with no findings; no open review threads
  • Reviewer focus: alias_copies in crates/socket-patch-core/src/crawlers/npm_crawler.rs. Links are never alias copies, and a dir whose name matches its own package name is skipped so it isn't counted twice. The hosted-VEX alias walk is now mostly redundant (noted as a follow-up).

Generated by Claude Code

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

npm agent-mode apply never patches an npm-aliased install (lp@npm:left-pad), yet VEX attests the package not_affected

2 participants