Skip to content

[BUG] npx installs bin symlink inside .store/ instead of the npx cache's top-level node_modules/.bin/ under install-strategy=linked #9252

@manzoorwanijk

Description

@manzoorwanijk

Is there an existing issue for this?

  • I have searched the existing issues

This issue exists in the latest npm version

  • I am using the latest npm

Current Behavior

When npx <cmd> installs a directory/file: package into the npx cache under install-strategy=linked, the bin symlink is created inside the store entry at ~/.npm/_npx/<hash>/node_modules/.store/<name>@<ver>-<hash>/node_modules/.bin/<cmd> but not at the npx cache's top-level ~/.npm/_npx/<hash>/node_modules/.bin/<cmd>.

libnpmexec appends <installDir>/node_modules/.bin to the child process PATH and then spawns a shell to run the command. Because the top-level .bin/ directory is empty (or nonexistent), the shell resolves <cmd> against the system PATH instead and prints command not found, even though the reify succeeded and the bin exists deep inside .store/.

This means npx is effectively broken for any file: directory spec under install-strategy=linked.

Registry packages under linked strategy are not affected — they correctly get top-level symlinks and bin links.

Observed output

npm warn reify The "linked" install strategy is EXPERIMENTAL and may contain bugs.
sh: my-tool: command not found

Expected Behavior

After reifying into the npx cache, <installDir>/node_modules/.bin/<cmd> must exist (either as a real symlink pointing to the installed package's bin target, or as a wrapper script). The PATH-based invocation inside libnpmexec depends on this.

Steps To Reproduce

rm -rf /tmp/host-app ~/.npm/_npx
mkdir -p /tmp/host-app/dist

cat > /tmp/host-app/package.json << 'EOF'
{
  "name": "host-app",
  "version": "1.0.0",
  "bin": { "my-tool": "dist/main.js" }
}
EOF

cat > /tmp/host-app/dist/main.js << 'EOF'
#!/usr/bin/env node
console.log('my-tool ran')
EOF
chmod +x /tmp/host-app/dist/main.js

cat > /tmp/host-app/.npmrc << 'EOF'
install-strategy=linked
EOF

cd /tmp/host-app
npm install
npx my-tool
# => npm warn reify The "linked" install strategy is EXPERIMENTAL and may contain bugs.
# => sh: my-tool: command not found

# Bin exists deep in .store/ but not at the top level
find ~/.npm/_npx -name my-tool
# => ~/.npm/_npx/<hash>/node_modules/.store/host-app@1.0.0-<hash>/node_modules/.bin/my-tool
ls ~/.npm/_npx/*/node_modules/.bin 2>&1
# => No such file or directory

Works with hoisted strategy

rm -rf /tmp/host-app/node_modules /tmp/host-app/package-lock.json /tmp/host-app/.npmrc ~/.npm/_npx
cd /tmp/host-app
npm install
npx my-tool
# => my-tool ran

Registry packages work under linked strategy

rm -rf ~/.npm/_npx
npx --install-strategy=linked cowsay@1.6.0 hello
# => works — cowsay binary is created at node_modules/.bin/cowsay

Environment

  • npm: 11.12.1
  • Node.js: v24.14.0
  • OS Name: macOS (Darwin 25.4.0)
  • npm config:
install-strategy=linked

Root Cause Analysis

The isolated reifier treats the file: package as a store-only entry without creating the top-level symlink and bin link that registry packages get.

For a registry package (e.g. cowsay@1.6.0) under linked strategy, the reifier creates:

  • node_modules/cowsay symlink pointing to .store/cowsay@1.6.0-<hash>/node_modules/cowsay
  • node_modules/.bin/cowsay bin link

For a file: directory spec, the reifier only creates:

  • .store/host-app@1.0.0-<hash>/node_modules/host-app (the package contents)
  • .store/host-app@1.0.0-<hash>/node_modules/.bin/my-tool (the bin, buried in the store)

No top-level node_modules/host-app symlink or node_modules/.bin/my-tool link is created.

libnpmexec/lib/index.js then does:

binPaths.push(resolve(installDir, 'node_modules/.bin'))

This points at the (missing) top-level .bin/. The bin inside .store/ is never added to PATH, so the spawn fails.

The fix should ensure that when the isolated reifier processes a file: directory spec as a dependency (not the root project), it creates the same top-level symlink and bin link structure that registry packages get.

Surfaced while investigating #9210.

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