Skip to content

@pnpm/exe 11.x ships node-gyp shims without execute bit, breaking native rebuilds #11483

@birtles

Description

@birtles

Verify latest release

  • I verified that the issue exists in the latest pnpm release

pnpm version

v11

Which area(s) of pnpm are affected? (leave empty if unsure)

Package manager compatibility

Link to the code that reproduces this issue or a replay of the bug

No response

Reproduction steps

Full disclosure: I used AI to help debug this issue and write the bug report but I have read the report and verified the details myself. I have also confirmed the proposed workaround (installing Node 24 first) fixes the issue for me on GitHub Actions.

# @pnpm/exe@11.0.6 — broken
mkdir -p /tmp/exe && curl -sL https://registry.npmjs.org/@pnpm/exe/-/exe-11.0.6.tgz | tar -xzf - -C /tmp/exe
ls -l /tmp/exe/package/dist/node-gyp-bin/node-gyp
# -rw-r--r--   <-- no execute bit

# pnpm@11.0.6 — correct
mkdir -p /tmp/pnpm && curl -sL https://registry.npmjs.org/pnpm/-/pnpm-11.0.6.tgz | tar -xzf - -C /tmp/pnpm
ls -l /tmp/pnpm/package/dist/node-gyp-bin/node-gyp

Three files in @pnpm/exe's dist/ lose their execute bit relative to the regular pnpm npm package:

Path pnpm@11.0.6 @pnpm/exe@11.0.6
dist/node-gyp-bin/node-gyp 0755 0644
dist/node-gyp-bin/node-gyp.cmd 0755 0644
dist/node_modules/node-gyp/bin/node-gyp.js 0755 0644

Every other file in dist/ has matching modes between the two packages, so the breakage is scoped specifically to the node-gyp shims.

End-to-end repro via pnpm/action-setup@v6 on GitHub Actions

pnpm/action-setup@v6 falls back to @pnpm/exe whenever the runner's system Node is < 22.13 (see run.ts). On ubuntu-latest (system Node 20.x at the time of writing), this means any project with a native module whose install script invokes node-gyp rebuild fails with:

.../node_modules/<pkg> install: sh: 1: node-gyp: Permission denied
[ELIFECYCLE] Command failed.

…because pnpm prepends dist/node-gyp-bin/ to the build script's PATH, and the shim there isn't executable.

Workaround for affected users: install Node ≥ 22.13 before pnpm/action-setup so the action takes the non-standalone path and uses the regular pnpm package. Or chmod +x the three files post-install.

Describe the Bug

@pnpm/exe@11.x packs dist/node-gyp-bin/node-gyp, dist/node-gyp-bin/node-gyp.cmd, and dist/node_modules/node-gyp/bin/node-gyp.js with mode 0644 instead of 0755. The same files in the regular pnpm npm package are correctly 0755. Any code path that exec's these shims (most importantly: a package's install script that calls node-gyp rebuild while pnpm has put dist/node-gyp-bin/ on PATH) fails with Permission denied.

This appears to be a v11 regression: @pnpm/exe@10.x did not ship a dist/ tree at all (its layout was just a single pnpm binary plus prepare/setup scripts), so this class of breakage couldn't occur. The reorganized @pnpm/exe@11 layout that bundles the full dist/ is what introduced the affected files — and they shipped with wrong modes.

The packaging defect very likely shares its root cause with #11398 (the fs.cpSync invocation in pnpm/artifacts/exe/scripts/build-artifacts.ts), since both involve dist/ content being staged for the @pnpm/exe artifact and ending up materially different from the source. #11398 was about symlink targets not being preserved verbatim; this one is about file modes being lost. A regression check that asserts mode & 0o111 on every file under dist/node-gyp-bin/ (and on dist/node_modules/node-gyp/bin/node-gyp.js) when packing @pnpm/exe would catch both classes.

Expected Behavior

@pnpm/exe@11.x should pack the three node-gyp shims with mode 0755, matching the regular pnpm package. Native modules whose install scripts invoke node-gyp rebuild should work under pnpm/action-setup@v6's standalone path identically to the non-standalone path.

Which Node.js version are you using?

Any version < 22.13

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)

Ubuntu

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No fields configured for Bug.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions