Skip to content

npx cannot discover packages installed globally via volta install #2112

Description

@Bacra

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:

  1. Check the bin field of the project's package.json (index.js:134-147)
  2. Check whether ./node_modules/.bin/<cmd> exists locally (index.js:150-154)
  3. Check whether ${globalBin}/<cmd> exists, where globalBin = $(npm prefix -g)/bin (index.js:155-157; defined in npm.js:453-456)
  4. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions