Is there an existing issue for this?
This issue exists in the latest npm version
This is not just a request to bump a dependency for a CVE
Current Behavior
When npm exec targets more than one workspace and the requested command resolves to a different workspace-local bin in each (same bin name, different package), every workspace runs the first workspace's bin instead of its own.
This is not specific to the linked install strategy — it reproduces identically under the default hoisted strategy.
The cause is module-level state in libnpmexec: binPaths is declared once at module scope (workspaces/libnpmexec/lib/index.js) and pushed to on each invocation, but never reset. npm exec --workspace runs callExec once per workspace in the same process, so the bin directory found for the first workspace stays at the front of the resolved bin list for every subsequent workspace, shadowing each workspace's own bin of the same name.
Expected Behavior
Each workspace targeted by npm exec should resolve and run its own local bin. Running npm exec -w a -w b -- shared-bin should execute a's shared-bin in a and b's shared-bin in b.
Steps To Reproduce
cd "$(mktemp -d)"
echo '{ "name": "root", "version": "1.0.0", "workspaces": ["packages/*"] }' > package.json
mkdir -p packages/a packages/b packages/tool-a packages/tool-b
echo '{ "name": "a", "version": "1.0.0", "dependencies": { "tool-a": "*" } }' > packages/a/package.json
echo '{ "name": "b", "version": "1.0.0", "dependencies": { "tool-b": "*" } }' > packages/b/package.json
echo '{ "name": "tool-a", "version": "1.0.0", "bin": { "shared-bin": "cli.js" } }' > packages/tool-a/package.json
echo '{ "name": "tool-b", "version": "1.0.0", "bin": { "shared-bin": "cli.js" } }' > packages/tool-b/package.json
printf '#!/usr/bin/env node\nconsole.log("A bin")\n' > packages/tool-a/cli.js
printf '#!/usr/bin/env node\nconsole.log("B bin")\n' > packages/tool-b/cli.js
chmod +x packages/tool-a/cli.js packages/tool-b/cli.js
npm install
npm exec -w a -w b -- shared-bin
# actual: "A bin" / "A bin"
# expected: "A bin" / "B bin"
Environment
- npm: 12.0.0-pre.1
- Node.js: 24.17.0
- OS Name: macOS 26.5.1
- System Model Name: MacBook Pro (Apple Silicon)
- npm config:
; copy and paste output from `npm config ls` here
Notes
Distinct from the linked-only #9616 (workspace-local bin ignored under linked). This bug affects all install strategies and only surfaces when a single npm exec spans multiple workspaces that each provide a same-named bin.
Is there an existing issue for this?
This issue exists in the latest npm version
This is not just a request to bump a dependency for a CVE
Current Behavior
When
npm exectargets more than one workspace and the requested command resolves to a different workspace-local bin in each (same bin name, different package), every workspace runs the first workspace's bin instead of its own.This is not specific to the linked install strategy — it reproduces identically under the default
hoistedstrategy.The cause is module-level state in libnpmexec:
binPathsis declared once at module scope (workspaces/libnpmexec/lib/index.js) and pushed to on each invocation, but never reset.npm exec --workspacerunscallExeconce per workspace in the same process, so the bin directory found for the first workspace stays at the front of the resolved bin list for every subsequent workspace, shadowing each workspace's own bin of the same name.Expected Behavior
Each workspace targeted by
npm execshould resolve and run its own local bin. Runningnpm exec -w a -w b -- shared-binshould executea'sshared-bininaandb'sshared-bininb.Steps To Reproduce
Environment
; copy and paste output from `npm config ls` hereNotes
Distinct from the linked-only #9616 (workspace-local bin ignored under linked). This bug affects all install strategies and only surfaces when a single
npm execspans multiple workspaces that each provide a same-named bin.