npm / npm/cli

[BUG] npx --yes <pkg> fails with 'sh: <bin>: command not found' — npx cache bin dir is not added to the spawned shell's PATH

Open
#9,870 0 comments 1 reaction 0 assignees View on GitHub

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:

  1. npm i -g <pkg> and invoke the bin directly (always works).
  2. 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).
  3. Prepend the cache bin to PATH for the npx invocation (works):
    export PATH="$(npm config get cache)/_npx/*/node_modules/.bin:$PATH"
    npx --yes @deepseek-ai/dsh web
    

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.