Skip to content

fix(libnpmexec): correctly resolve workspace dependency binaries from graph#9650

Open
arjun-vegeta wants to merge 3 commits into
npm:latestfrom
arjun-vegeta:fix-exec-binpaths
Open

fix(libnpmexec): correctly resolve workspace dependency binaries from graph#9650
arjun-vegeta wants to merge 3 commits into
npm:latestfrom
arjun-vegeta:fix-exec-binpaths

Conversation

@arjun-vegeta

@arjun-vegeta arjun-vegeta commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Description:
Fixes #9640

Bug:
When running npm exec with a workspace-scoped dependency that shares a binary name with a root-hoisted dependency from another workspace, the root-hoisted binary is incorrectly executed instead of the locally specified dependency.

Root Cause:
Under workspace hoisting, multiple workspaces might use tools that expose identically-named binaries (e.g. shared-bin). Because npm links hoisted dependencies to node_modules/.bin at the root, the root .bin directory will only contain a single symlink for shared-bin (pointing to whichever package got hoisted).
libnpmexec was previously falling back to searching .bin directories via localFileExists(), meaning it would always incorrectly execute the hoisted root symlink, completely ignoring the specific dependency version declared by the active workspace.

Fix:

  • Detect when npm exec is invoked from within a workspace (opts.pkgPath !== path).
  • Hook into the arborist graph and directly traverse the edgesOut of the specific workspace node to find the correctly scoped dependency's bin path natively.
  • Skip the hoisted .bin fallback search entirely when inside a workspace context, effectively isolating executions from root-hoisted symlink collisions.

Testing:

  • Added a full integration test in test/local.js that exactly mimics the shared-bin hoist collision from the bug report.
  • All tests pass 100%.

@arjun-vegeta arjun-vegeta requested review from a team as code owners June 24, 2026 20:07

@owlstronaut owlstronaut left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for digging into this, but I ran your exact repro on the branch and it still prints  A bin / A bin  under workspace hoisting only one  shared-bin  link gets created (→tool-a), so the  binPaths  change doesn't actually fix #9640

@arjun-vegeta

Copy link
Copy Markdown
Contributor Author

Thanks for digging into this, but I ran your exact repro on the branch and it still prints  A bin / A bin  under workspace hoisting only one  shared-bin  link gets created (→tool-a), so the  binPaths  change doesn't actually fix #9640

Thanks for testing it out. The old binPaths approach was a dead end because of how the hoisted .bin symlink gets overwritten at the root

I just pushed a complete rewrite. I dropped the binPaths idea entirely and instead hooked into the Arborist tree. Now, if it detects it's running inside a workspace, it walks the actual dependency graph (edgesOut) for that specific workspace node to find the correct binary path directly (e.g., node_modules/tool-b/cli.js), completely bypassing the hoisted .bin folder

The repro now outputs A bin / B bin correctly, and I've added full integration tests for the hoisted edge-case. Can you test it out once more and let me know how it goes

@arjun-vegeta arjun-vegeta changed the title fix(exec): prevent shared binPaths pollution across workspace runs fix(libnpmexec): correctly resolve workspace dependency binaries from graph Jun 25, 2026
@arjun-vegeta arjun-vegeta requested a review from owlstronaut June 25, 2026 10:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] npm exec -w a -w b -- <bin> runs the first workspace's bin for every workspace

2 participants