Skip to content

PnP cannot resolve a file inside a package archive when the global folder is behind a symlink #7303

Open

Description

@JounQin

Describe the bug

With PnP, when the global folder (default $HOME/.yarn/berry) is reached through a symlinked path, pnpapi.resolveRequest hands Node a realpath'd filename for a package served from an archive, while the PnP map records that archive under the symlinked spelling. A relative require from inside the archive is then resolved against the realpath and fails:

Error: Cannot find module './dep.cjs'
Require stack:
- /volume1/homes/<user>/.yarn/berry/cache/repro-pkg-file-c28e1974f5-10c0.zip/node_modules/repro-pkg/index.js
- /tmp/repro/consumer/main.js
    at require$$0.Module._resolveFilename (.pnp.cjs:5924:54)

The file is in the archive — unzip -l lists node_modules/repro-pkg/dep.cjs — and a package subpath resolves fine: require('repro-pkg/dep.cjs') prints dep loaded. Only the entry's relative require('./dep.cjs') fails.

To reproduce

# $HOME here is /var/services/homes/<user>, a symlink to /volume1/homes/<user> (Synology DSM)

# a package whose entry requires a file beside it
mkdir -p /tmp/repro/stage/package /tmp/repro/consumer/.yarn/releases
cat > /tmp/repro/stage/package/package.json <<'JSON'
{ "name": "repro-pkg", "version": "1.0.0", "main": "index.js" }
JSON
echo "module.exports = require('./dep.cjs')" > /tmp/repro/stage/package/index.js
echo "module.exports = 'dep loaded'"         > /tmp/repro/stage/package/dep.cjs
(cd /tmp/repro/stage && tar czf /tmp/repro/repro-pkg.tgz package)

# a PnP consumer that installs it from the tarball
cp /path/to/yarn-4.18.1.cjs /tmp/repro/consumer/.yarn/releases/
printf 'nodeLinker: pnp\nyarnPath: .yarn/releases/yarn-4.18.1.cjs\n' > /tmp/repro/consumer/.yarnrc.yml
cat > /tmp/repro/consumer/package.json <<'JSON'
{ "name": "consumer", "private": true, "dependencies": { "repro-pkg": "file:../repro-pkg.tgz" } }
JSON
echo "console.log(require('repro-pkg'))" > /tmp/repro/consumer/main.js

cd /tmp/repro/consumer
yarn install
yarn node main.js

The two APIs disagree

$ node -r ./.pnp.cjs -e "const p=require('pnpapi');console.log('toUnqualified:',p.resolveToUnqualified('repro-pkg',process.cwd()+'/main.js'));console.log('request:      ',p.resolveRequest('repro-pkg',process.cwd()+'/main.js'))"
toUnqualified: /var/services/homes/<user>/.yarn/berry/cache/repro-pkg-file-c28e1974f5-10c0.zip/node_modules/repro-pkg/
request:       /volume1/homes/<user>/.yarn/berry/cache/repro-pkg-file-c28e1974f5-10c0.zip/node_modules/repro-pkg/index.js

Adding the realpath of that same folder makes it pass, with the same archive file:

# .yarnrc.yml
globalFolder: /volume1/homes/<user>/.yarn/berry   # instead of the symlinked spelling
$ yarn node main.js
dep loaded

Expected behaviour

dep loaded. A file inside an installed package should resolve regardless of whether an ancestor of the cache folder is a symlink.

Environment

  • Yarn 4.18.1 (a project pinned to 4.15.0 fails the same way)
  • Node 24.21.0
  • Synology DSM: $HOME=/var/services/homes/<user> → /volume1/homes/<user>. The same shape occurs on macOS (/var → /private/var) or any symlinked $HOME/global folder.

Notes

  • --preserve-symlinks / --preserve-symlinks-main do not help: the realpath comes from Yarn's own resolveRequest, so Node's symlink handling is never involved. (Applied globally they do break Yarn's own bootstrap: Cannot find module './lib/corepack.cjs'.)
  • Presumably the hook matches the request's parent filename against the recorded package location; a realpath'd parent no longer matches, so the relative request falls through to the default resolver, which cannot stat a path inside a zip. That would also explain why a package subpath works and a relative request does not.
  • Workarounds: enableGlobalCache: false puts the cache inside the project; globalFolder set to the realpath of the default keeps the shared cache (same zip, same hash).
  • Fix direction: don't return a realpath'd filename for a zip package — or match package locations after canonicalising both sides — when the recorded location is reached through a symlink.
  • Related: fix(pnpapi): dedupe pnpapi instances where .pnp.cjs is a symlink #5674 (dedupe pnpapi where .pnp.cjs is a symlink), [Bug?]: Global/Local Cache Not Shareable Between Workspaces #6049 (cache sharing), [Bug?]: globalFolder is not ${HOME}/.yarn/berry on Windows as documented #6814 (globalFolder on Windows).

Activity

  1. tarun5004 commented on Oct 26, 2026

    @tarun5004

    Hi, I'd like to help investigate this PnP resolution issue using Codex assistance. I'd start with a temporary symlinked globalFolder and a small local package whose entry requires a sibling file, then compare resolveToUnqualified and resolveRequest. I haven't run the reproduction yet. Is this still available for a community contributor? If it is, could you assign it to @tarun5004 and confirm the preferred fix direction before I change resolution behavior?

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