Verify latest release
- I verified that the issue exists in the latest pnpm release
pnpm version
11.17.0
Which area(s) of pnpm are affected? (leave empty if unsure)
CLI
Link to the code that reproduces this issue or a replay of the bug
No response
Reproduction steps
- Install a package with pnpm somewhere off the default global-bin path, e.g.:
mkdir -p /opt/example-tool && cd /opt/example-tool
pnpm init
pnpm add some-pkg-with-a-bin
pnpm generates a POSIX shell shim at node_modules/.bin/<bin-name> containing (abbreviated):
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
...
exec "$basedir/node" "$basedir/../.pnpm/some-pkg@1.2.3/node_modules/some-pkg/bin/cli.js" "$@"
- Symlink that shim onto PATH from an unrelated directory, e.g.:
ln -s /opt/example-tool/node_modules/.bin/<bin-name> /usr/local/bin/<bin-name>
(This is a normal pattern for tools that install each package into its own isolated directory under /opt and expose a single stable symlink on PATH, rather than using pnpm's own global-bin mechanism.)
- Run the tool via the PATH symlink:
<bin-name>
Describe the Bug
The shim computes basedir from dirname "$0". POSIX shells do not resolve symlinks when setting $0 - they use the path that was actually invoked. So when the shim is reached through an external symlink, $0 is the symlink's path (e.g. /usr/local/bin/<bin-name>), not the shim's real location (/opt/example-tool/node_modules/.bin/<bin-name>).
basedir therefore resolves to /usr/local/bin instead of /opt/example-tool/node_modules/.bin, and the shim's relative traversal into the pnpm store ($basedir/../.pnpm/...) resolves against the wrong directory entirely - e.g. /usr/local/.pnpm/some-pkg@1.2.3/node_modules/some-pkg/bin/cli.js, which does not exist:
node:internal/modules/cjs/loader:1573
throw err;
^
Error: Cannot find module '/usr/local/.pnpm/some-pkg@1.2.3/node_modules/some-pkg/bin/cli.js'
at Module._resolveFilename (node:internal/modules/cjs/loader:1569:15)
...
code: 'MODULE_NOT_FOUND'
Running the shim directly (/opt/example-tool/node_modules/.bin/<bin-name>) works fine, confirming the shim itself is correct and the bug is purely in how basedir is derived when reached via an additional layer of symlink indirection.
This works without issue when following the same steps with npm.
Expected Behavior
The shim should resolve its own real location (e.g. via readlink -f "$0"-equivalent logic, following symlinks) before deriving basedir, so it continues to work regardless of how many symlink hops were used to invoke it - the same way tools like npm's bin shims / env -S resolution are generally expected to behave when placed on PATH via a symlink.
Which Node.js version are you using?
26.5.0
Which operating systems have you used?
If your OS is a Linux based, which one it is? (Include the version if relevant)
No response
Verify latest release
pnpm version
11.17.0
Which area(s) of pnpm are affected? (leave empty if unsure)
CLI
Link to the code that reproduces this issue or a replay of the bug
No response
Reproduction steps
node_modules/.bin/<bin-name>containing (abbreviated):ln -s /opt/example-tool/node_modules/.bin/<bin-name> /usr/local/bin/<bin-name>(This is a normal pattern for tools that install each package into its own isolated directory under /opt and expose a single stable symlink on PATH, rather than using pnpm's own global-bin mechanism.)
<bin-name>Describe the Bug
The shim computes
basedirfromdirname "$0". POSIX shells do not resolve symlinks when setting$0- they use the path that was actually invoked. So when the shim is reached through an external symlink,$0is the symlink's path (e.g./usr/local/bin/<bin-name>), not the shim's real location (/opt/example-tool/node_modules/.bin/<bin-name>).basedirtherefore resolves to/usr/local/bininstead of/opt/example-tool/node_modules/.bin, and the shim's relative traversal into the pnpm store ($basedir/../.pnpm/...) resolves against the wrong directory entirely - e.g./usr/local/.pnpm/some-pkg@1.2.3/node_modules/some-pkg/bin/cli.js, which does not exist:Running the shim directly (
/opt/example-tool/node_modules/.bin/<bin-name>)works fine, confirming the shim itself is correct and the bug is purely in howbasediris derived when reached via an additional layer of symlink indirection.This works without issue when following the same steps with
npm.Expected Behavior
The shim should resolve its own real location (e.g. via
readlink -f "$0"-equivalent logic, following symlinks) before derivingbasedir, so it continues to work regardless of how many symlink hops were used to invoke it - the same way tools like npm's bin shims /env -Sresolution are generally expected to behave when placed onPATHvia a symlink.Which Node.js version are you using?
26.5.0
Which operating systems have you used?
If your OS is a Linux based, which one it is? (Include the version if relevant)
No response