[BUG] npx --yes <pkg> fails with 'sh: <bin>: command not found' — npx cache bin dir is not added to the spawned shell's PATH
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 10.1k
- Forks
- 4.7k
- Avg merge
- 2d 2h
- Merged PRs (30d)
- 19
Description
Is there an existing issue for this?
- I have searched the existing issues
Summary
npx --yes <pkg> <args> (and npm exec -- <pkg> <args>) downloads the package into ~/.npm/_npx/<hash>/node_modules/ and creates a bin symlink at ~/.npm/_npx/<hash>/node_modules/.bin/<bin-name>, but then runs the command via sh -c "<bin-name> <args>" without putting the npx cache bin directory in the spawned shell's PATH. The shell therefore cannot find the bin by name and reports sh: <bin-name>: command not found.
Running the same package via node <cache>/node_modules/<pkg>/<bin-path> works perfectly, and the issue reproduces regardless of Node major version (verified on Node 20 and Node 22) and npm major version (verified on npm 10.9.4 and npm 11.x).
Current Behavior
$ npx --yes @deepseek-ai/dsh web
sh: dsh: command not found
The package is downloaded (visible at ~/.npm/_npx/<hash>/node_modules/@deepseek-ai/dsh/) and the bin symlink is created:
$ ls -l ~/.npm/_npx/<hash>/node_modules/.bin/dsh
lrwxr-xr-x ... dsh -> ../@deepseek-ai/dsh/lib/bin.js
But the sh -c "dsh web" invocation that npm uses cannot resolve dsh because the npx cache's bin directory is not on PATH. After failing, npm exits 0 with no other diagnostic, leaving users with no actionable feedback.
Expected Behavior
npx --yes @deepseek-ai/dsh web should succeed, just like npm i -g @deepseek-ai/dsh && dsh web does, because both place the bin in the user's PATH.
Steps To Reproduce
mkdir /tmp/repro && cd /tmp/repro
npx --yes @deepseek-ai/dsh web
# → sh: dsh: command not found
Any package whose package.json declares a bin (and the bin file is a Node script) reproduces this on the current npm, e.g.:
mkdir /tmp/repro2 && cd /tmp/repro2
npx --yes http-server
# → sh: http-server: command not found
(I verified this against http-server@14.1.1 on npm 11.x in addition to @deepseek-ai/dsh@0.1.0-rc.6.)
Environment
- npm: 11.6.2 (verified; also reproduces on 11.1.0 and 10.9.4)
- Node.js: v22.20.0 (verified; also reproduces on v20.18.3)
- OS: macOS 14 (Darwin 24.6.0)
Root Cause
libnpmexec → @npmcli/run-script (run-script-pkg.js) → @npmcli/promise-spawn. The shell command is built by make-spawn-args.js, and the PATH environment variable for the child shell is assembled by set-path.js:
// node_modules/@npmcli/run-script/lib/set-path.js (lines 22–28)
let p = projectPath // projectPath === process.cwd()
let pp
do {
pathArr.push(resolve(p, 'node_modules', '.bin'))
pp = p
p = dirname(p)
} while (p !== pp)
This loop walks up from process.cwd() looking for node_modules/.bin at every ancestor directory, then appends the inherited PATH last. It never includes ~/.npm/_npx/<hash>/node_modules/.bin — the actual location of the bin symlink that libnpmexec just created. The binPaths parameter that libnpmexec could pass is also not honored for this purpose.
Meanwhile, promise-spawn ultimately invokes sh -c "<bin-name> <args>" (see node_modules/@npmcli/promise-spawn/lib/index.js:73,121), so a missing PATH entry means sh cannot find the bin by name and exits with command not found. The exit status is non-zero, but libnpmexec/run-script.js does not surface it to the user.
This is independent of Node version and npm version (verified by running node /tmp/npm10/package/bin/npx-cli.js from a downloaded npm 10.9.4 tarball on Node 22.20, which reproduces the exact same error).
Suggested Fix
In @npmcli/run-script/lib/run-script-pkg.js (or libnpmexec), when binPaths is provided by libnpmexec and includes the npx cache bin directory, prepend them to pathArr in set-path.js so the spawned shell can resolve bin names. A minimal patch would be to honor the existing binPaths argument in set-path.js (currently it only uses binPaths when the caller explicitly passes binPaths: true, conflating "should I use binPaths?" with "are binPaths provided?"):
// set-path.js — treat binPaths as a list of dirs to prepend, not a boolean
if (binPaths && Array.isArray(binPaths)) {
pathArr.push(...binPaths)
}
…or, in libnpmexec, pass the npx cache bin dir explicitly when invoking run-script.js.
Workarounds
Until this is fixed, users can:
npm i -g <pkg>and invoke the bin directly (always works).- Invoke the bin via the cache path:
node $(npm config get cache)/_npx/<hash>/node_modules/<pkg>/<bin-path>(works once the cache is populated). - Prepend the cache bin to
PATHfor the npx invocation (works):export PATH="$(npm config get cache)/_npx/*/node_modules/.bin:$PATH" npx --yes @deepseek-ai/dsh web
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with libnpmexec and follow its run-script.js call into @npmcli/run-script/lib/run-script-pkg.js, lib/set-path.js, and @npmcli/promise-spawn/lib/index.js. Reproduce the npx --yes http-server case, inspect how binPaths reaches set-path.js, and verify that the cache .bin directory is available to the spawned shell and failures are surfaced.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, node.js
- Domain
- cli, developer-experience, tooling
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 65/100