Skip to content

Bin shim's basedir=$(dirname "$0") breaks when reached via an external symlink #13405

Description

@globau

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

  1. 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" "$@"
    
  2. 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.)
  3. 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?

  • macOS
  • Windows
  • Linux

If your OS is a Linux based, which one it is? (Include the version if relevant)

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: binstubsstate: acceptedThe required changes are defined. There is consensus on the change. Development can be startedtype: bug

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions