Environment
- Volta: 2.0.2
- OS: Linux (Ubuntu)
- Node: 20.20.2 (default, via Volta)
- npm: 10.9.9 (default, via Volta)
Description
Packages installed globally via volta install <package> work fine when invoked directly in the shell (the shim lives in ~/.volta/bin/ which is on PATH). However, npx <command> cannot discover these commands — npx ignores packages installed globally by Volta and instead downloads a fresh copy from the registry.
Steps to reproduce
# 1. Install a tool globally via Volta
volta install prettier
# package prettier@3.9.6 / prettier / node@20.20.2
# 2. Invoke directly in the shell — works fine
prettier --version
# Output: 3.9.6 ✅
# 3. Enter an empty directory without prettier installed
mkdir /tmp/empty-project && cd /tmp/empty-project
# 4. Invoke via npx — not found, triggers a temporary download
npx prettier --version
# Prompt: "Need to install the following packages: prettier@3.9.6. Ok to proceed?" ❌
# 5. Verify with --no-install that npx indeed considers the package missing
npx --no-install prettier --version
# Error: "npx canceled due to missing packages and no YES option: [\"prettier@3.9.6\"]" ❌
Expected behavior
npx prettier should discover and reuse the prettier installed globally by Volta (consistent with how npx reuses npm install -g prettier in a non-Volta environment), avoiding a redundant download.
Actual behavior
npx reports the package as missing and attempts a temporary download from the registry.
Root cause analysis
In npm 10.9.9, the lookup logic of npx <cmd> (without --package) lives in libnpmexec/lib/index.js and consists of 4 steps — none of which search PATH for arbitrary executables:
- Check the
bin field of the project's package.json (index.js:134-147)
- Check whether
./node_modules/.bin/<cmd> exists locally (index.js:150-154)
- Check whether
${globalBin}/<cmd> exists, where globalBin = $(npm prefix -g)/bin (index.js:155-157; defined in npm.js:453-456)
- Check whether the package exists in the global node_modules tree (
$(npm root -g)) and its bin is present under globalBin (index.js:204-218)
If none of the 4 steps match, npx downloads the package from the registry into the npx cache.
Under Volta:
npm prefix -g
# → /home/<user>/.volta/tools/image/node/20.20.2
# i.e. globalBin = /home/<user>/.volta/tools/image/node/20.20.2/bin
# This directory only contains runtime commands such as node/npm/corepack,
# not packages installed by users via volta install
ls /home/<user>/.volta/tools/image/node/20.20.2/lib/node_modules/
# → only npm / corepack, no user-installed global packages
# Packages installed via volta install actually live in:
ls ~/.volta/tools/image/packages/prettier/bin/
# → prettier (symlink → ../lib/node_modules/prettier/bin/prettier.cjs)
In other words, volta install stores global packages in ~/.volta/tools/image/packages/<pkg>/ and exposes their bins via shims in ~/.volta/bin/ (visible only to the shell via PATH). However, the locations npx relies on — $(npm prefix -g)/bin and the global node_modules tree, both of which are bound to the node image — do not contain these packages, so all 4 lookup steps miss.
For comparison: if a command happens to live in the node image's bin directory (e.g. tools installed directly via npm install -g into the node image), npx does find it via step 3 (verified: npx --no-install node --version works). This confirms the problem is not with npx's lookup mechanism itself, but that the storage location of volta install packages is invisible to npx.
Possible improvement
Expose volta install packages to $(npm prefix -g)/bin or the global node_modules tree in some way (e.g. via symlinks), so that npx's existing lookup logic can find them.
Environment
Description
Packages installed globally via
volta install <package>work fine when invoked directly in the shell (the shim lives in~/.volta/bin/which is onPATH). However,npx <command>cannot discover these commands — npx ignores packages installed globally by Volta and instead downloads a fresh copy from the registry.Steps to reproduce
Expected behavior
npx prettiershould discover and reuse the prettier installed globally by Volta (consistent with how npx reusesnpm install -g prettierin a non-Volta environment), avoiding a redundant download.Actual behavior
npx reports the package as missing and attempts a temporary download from the registry.
Root cause analysis
In npm 10.9.9, the lookup logic of
npx <cmd>(without--package) lives inlibnpmexec/lib/index.jsand consists of 4 steps — none of which searchPATHfor arbitrary executables:binfield of the project'spackage.json(index.js:134-147)./node_modules/.bin/<cmd>exists locally (index.js:150-154)${globalBin}/<cmd>exists, whereglobalBin = $(npm prefix -g)/bin(index.js:155-157; defined innpm.js:453-456)$(npm root -g)) and its bin is present under globalBin (index.js:204-218)If none of the 4 steps match, npx downloads the package from the registry into the npx cache.
Under Volta:
In other words,
volta installstores global packages in~/.volta/tools/image/packages/<pkg>/and exposes their bins via shims in~/.volta/bin/(visible only to the shell viaPATH). However, the locations npx relies on —$(npm prefix -g)/binand the global node_modules tree, both of which are bound to the node image — do not contain these packages, so all 4 lookup steps miss.For comparison: if a command happens to live in the node image's bin directory (e.g. tools installed directly via
npm install -ginto the node image), npx does find it via step 3 (verified:npx --no-install node --versionworks). This confirms the problem is not with npx's lookup mechanism itself, but that the storage location ofvolta installpackages is invisible to npx.Possible improvement
Expose
volta installpackages to$(npm prefix -g)/binor the global node_modules tree in some way (e.g. via symlinks), so that npx's existing lookup logic can find them.