Skip to content

Fix npm alias links missed from a member (#1494) - #1500

Merged
Mikola Lysenko (mikolalysenko) merged 4 commits into
mainfrom
agent/fix-npm-member-alias-link-lookup
Oct 13, 2026
Merged

Mikola Lysenko (mikolalysenko) merged 4 commits into
mainfrom
agent/fix-npm-member-alias-link-lookup

Conversation

@mikolalysenko

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

Copy link
Copy Markdown
Collaborator

LLM Description written by Claude Code:claude-opus-5-5

Fixes #1494

Summary

When run from inside a vlt, pnpm or npm-linked workspace member, apply reported the member's npm alias dependency ("lp": "npm:left-pad@1.3.0") as package_not_installed and exited 1. rollback couldn't restore that dependency either, even though scan had offered the patch through the same link. Both commands now patch and restore it.

Root cause

From a member directory, the npm resolver's only root is the member's own node_modules. Under a root-store layout the alias entry packages/a/node_modules/lp is just a symlink into the root store. The resolver's direct probe looks for node_modules/left-pad, and its alias pass skips links on purpose (#856), so no copy was found. #1470 had already closed the same gap for path-scoped matching only (find_package_paths_for_path_scope + npm_links_to).

Fix (crates/socket-patch-cli/src/ecosystem_dispatch.rs)

  • The root-link pass moves into a shared dispatch_find_with_npm_links. It is now used by path scoping (every link, as before) and by the copy lookups: find_all_packages_for_purls (apply), find_all_packages_for_rollback[_reusing] (rollback) and find_packages_for_rollback_reusing (get, vendor).
  • find_manifest_package_copies_reusing (apply --check, vex) gets the same unresolved-only pass (Bugbot finding: run from the member, --check reported the patched copy as package_not_installed). Two vex_consumed tests whose setup assumed the resolver never returns a link now check that it returns the same copies as the identity fallback.
  • The copy lookups use links only for purls that have no resolved copy (NpmLinks::ForUnresolved). The pre-ready /code-review high showed why this matters: when the resolver has already found a copy, the apply engine's store fan-out reaches every peer variant of it. Taking a member link to another variant on top of that visited it twice and brought back Agent-mode apply in a pnpm workspace reports each member-linked package twice, inflating the --json skipped count with duplicate already_patched events #633's phantom already_patched / already-original event. Restricting the pass also keeps a first-party file: alias link next to a patched registry copy from turning a clean run into a refusal. And the pass is skipped entirely when every purl resolves, so the common case pays nothing extra.
  • Nothing changes in the npm, PyPI or gem wrappers: they only dispatch to the binary.

Test evidence

Issue Test Without fix With fix
#1494 (vlt, pnpm, npm .store) apply::in_process_npm_multicopy::apply_and_rollback_reach_a_member_alias_link_from_the_member: apply, re-apply and rollback from the member, then apply and rollback from the root (one visit) FAILED: package_not_installed, partialFailure ok
#1494 (unit) ecosystem_dispatch::tests::multi_copy_lookups_take_a_member_alias_link_into_the_root_store FAILED ok
Bugbot: --check misses member alias the member test's apply --check step FAILED: package_not_installed ok
review finding (#633 regression) apply::in_process_npm_multicopy::apply_and_rollback_visit_a_member_alias_peer_variant_once FAILED against the first fix commit (0a81315) ok

I also reproduced the issue with real pnpm 10.28: a workspace with packages/a depending on "lp": "npm:left-pad@1.3.0" and a hand-staged manifest. Run from packages/a with the fixed binary, apply --offline exits 0 with applied, and the root store's left-pad/index.js is patched (require.resolve('lp') resolves to it). rollback then gives success with rolledBack: 1 and restores the bytes.

Local runs:

  • cargo fmt --all -- --check: clean
  • cargo clippy --workspace --all-features -- -D warnings: clean
  • cargo test -p socket-patch-core -p socket-patch-cli --all-features --lib --test apply --test rollback --test scan --test get --test in_process_vendor --test cli: every CLI target passes (920 lib, 135 apply, 111 rollback, 87, 133, 59, 131). 4 socket-patch-core lib tests fail locally because they rely on read-only permissions and this sandbox runs as root (e.g. copy_tree::relax_loop_must_not_traverse_symlinked_root: "removal cannot succeed under a read-only parent"). This PR doesn't touch core, so CI is the check there.
  • A full cargo test --workspace ran out of this sandbox's disk allowance while linking test binaries, so I ran the targeted suites above instead. CI covers the rest.

Review findings not fixed

  • "get_node_modules_paths errors are swallowed and the walk is retried": this was already the behavior in path scoping, and NpmCrawler::get_node_modules_paths always returns Ok, so there's no real failure path.

🤖 Generated with Claude Code

https://claude.ai/code/session_0195pcxXZ2GXtByX1L8egMhJ


Note

Medium Risk
Changes core npm package discovery for apply, rollback, check, and vex; behavior is gated to unresolved PURLs and covered by extensive regression tests for #633 and #1494.

Overview
Fixes #1494: when apply, rollback, apply --check, or vex run from a workspace member, npm alias dependencies that only exist as a root node_modules symlink (vlt/pnpm/npm linked layouts) are no longer reported as package_not_installed.

Package resolution now goes through shared dispatch_find_with_npm_links, which runs the normal crawl and then optionally appends matching root-level symlinks via npm_links_to. Copy lookups (find_all_packages_for_purls, rollback variants, find_manifest_package_copies_reusing) use NpmLinks::ForUnresolved so links are added only when the crawler found no install path—avoiding a second visit when the root store copy already exists (#633 phantom already_patched). Path scoping still uses NpmLinks::All (#1266).

Tests cover member-alias apply/rollback/check across store layouts, resolver behavior from member vs root, and peer-variant alias links visited once from the repo root.

Reviewed by Cursor Bugbot for commit 6104e76. Configure here.


Generated by Claude Code

Assisted-by: Claude Code:claude-opus-5-5
Run from inside a vlt, pnpm or npm-linked workspace member, apply
reported a member's npm alias dependency ("lp": "npm:left-pad@1.3.0")
as package_not_installed and exited 1, and rollback could not restore
it, although scan had offered the patch. The member's node_modules is
the only root walked there, and its alias entry is only a link into
the root store, which the resolver never takes as a copy.

Apply's and rollback's lookups now add the root links whose target is
a requested package, the same pass path scoping got in #1470. Both
commands already act on each physical copy once, so a run from the
root still patches the store copy a single time.

Fixes #1494

Assisted-by: Claude Code:claude-opus-5-5
Apply, rollback, get and vendor now take a workspace member's alias
link only for a package they found no other copy of. When a copy was
found, apply already patches every store variant of it, so taking the
link as well visited one variant twice and reported a phantom
already_patched (or already-original on rollback). It also kept a
first-party alias link beside a patched registry copy from turning a
clean run into a refusal, and skips the link pass whenever every
package resolves.

Path-scoped scan and rollback still match every link.

Assisted-by: Claude Code:claude-opus-5-5
@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.

Stale Bugbot comment from a previous run.

Comment thread crates/socket-patch-cli/src/ecosystem_dispatch.rs
Run from a workspace member, apply now patches an alias dependency
through the member's link into the root store, but apply --check and
vex looked copies up without that link. The check reported the package
as not installed and exited 0. They now use the same lookup as apply,
which takes a root link only for a package with no other copy.

Two vex_consumed tests assumed the resolver never returns a link. They
now check that it returns the same copies the identity fallback finds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0195pcxXZ2GXtByX1L8egMhJ
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

bugbot run


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 6104e76. Configure here.

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

Copy link
Copy Markdown
Collaborator Author

Ready for review (burn-down agent)


Generated by Claude Code

Merged via the queue into main with commit f7ef1f3 Oct 13, 2026
54 checks passed
@mikolalysenko
Mikola Lysenko (mikolalysenko) deleted the agent/fix-npm-member-alias-link-lookup branch October 13, 2026 22:59
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

3 participants