Skip to content

fix(pnp): provide source for commonjs files inside zip archives#7086

Open
tellnes wants to merge 1 commit intoyarnpkg:masterfrom
tellnes:fix/pnp-esm-cjs-zip-source
Open

fix(pnp): provide source for commonjs files inside zip archives#7086
tellnes wants to merge 1 commit intoyarnpkg:masterfrom
tellnes:fix/pnp-esm-cjs-zip-source

Conversation

@tellnes
Copy link
Copy Markdown
Contributor

@tellnes tellnes commented Mar 28, 2026

What's the problem this PR addresses?

The PnP ESM loader fails with EBADF: bad file descriptor, fstat on Node.js >= 25.7.0 when ESM code imports a CommonJS package from a zip archive.

Bisected results:

Node version Status
24.14.1 Works
25.0.0 Works
25.4.0 Works
25.6.0 Works
25.6.1 Works
25.7.0 Broken
25.8.1 Broken

The regression was introduced by nodejs/node#61769 ("lib: reduce cycles in esm loader and load it in snapshot"), which changed how createCJSModuleWrap handles null source. When the PnP loader returns undefined source for CJS modules, Node's fallback now calls getSourceSync() which tries to readFileSync the zip-internal path and fails.

Error:

node:fs:386
  const stats = binding.fstat(fd, false, undefined, true /* shouldNotThrow */);
                        ^

Error: EBADF: bad file descriptor, fstat
    at tryStatSync (node:fs:386:25)
    at readFileSync (node:fs:442:17)
    at getSourceSync (node:internal/modules/esm/load:37:14)
    at createCJSModuleWrap (node:internal/modules/esm/translators:204:32)

How did you fix it?

For CommonJS files inside zip archives (.zip/ in path), the PnP load hook now returns the source content instead of undefined. This prevents Node from trying to read the file from disk.

For CJS files on the real filesystem, undefined is still returned to preserve the CJS monkey-patching behavior (cf. #5677).

Checklist

  • I have read the Contributing Guide.
  • I have set the packages that need to be released for my changes to be effective.
  • I will check that all automated PR checks pass before the PR gets reviewed.

The ESM load hook returned undefined source for all CJS modules,
which worked because Node falls back to reading the file itself.
Node 25.7.0 changed this fallback to use getSourceSync(), which
fails with EBADF on zip-internal paths since they aren't real
filesystem entries. Source is now provided for CJS files inside
zip archives while preserving the undefined return for local CJS
files, which is needed for CJS monkey-patching compatibility.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant