Skip to content

fix: make postinstall spawn-helper chmod work under pnpm GVS - #8

Merged
myobie merged 1 commit into
compoundingtech:mainfrom
schickling:fix/postinstall-pnpm-gvs
Apr 7, 2026
Merged

fix: make postinstall spawn-helper chmod work under pnpm GVS#8
myobie merged 1 commit into
compoundingtech:mainfrom
schickling:fix/postinstall-pnpm-gvs

Conversation

@schickling

Copy link
Copy Markdown
Contributor

Summary

The current postinstall workaround silently no-ops under pnpm with enableGlobalVirtualStore, leaving node-pty's spawn-helper non-executable and causing posix_spawnp failed at runtime.

Repro

mkdir scratch && cd scratch
pnpm init
pnpm add @myobie/pty
ls -l node_modules/.pnpm/node-pty@*/node_modules/node-pty/prebuilds/darwin-arm64/spawn-helper
# -rw-r--r--  ← should be 755
node -e "import('@myobie/pty/testing').then(m => m.Session.spawn('echo', ['hi']))"
# Error: posix_spawnp failed.

Root cause

The existing postinstall:

"postinstall": "chmod +x node_modules/node-pty/prebuilds/*/spawn-helper 2>/dev/null || true"

uses a relative path that only resolves in flat node_modules layouts. Under pnpm's content-addressed store (especially with enableGlobalVirtualStore), @myobie/pty's lifecycle CWD is ~/Library/pnpm/store/v11/links/@myobie/pty/0.4.1/<hash>/node_modules/@myobie/pty/, and node-pty lives in a sibling content-addressed link — not nested. The relative node_modules/node-pty/... path doesn't exist there. The chmod fails, 2>/dev/null || true swallows the error, and the user is left with a non-executable binary.

The root cause is in node-pty itself (its published tarball ships spawn-helper with mode 0644 and its own post-install never chmods it). The fix is already merged upstream in microsoft/node-pty#858 and #866, but node-pty@1.1.0 (the latest published) predates those merges. So this @myobie/pty workaround needs to actually work until a newer node-pty ships.

The fix

Replace the broken shell chmod with scripts/postinstall.js:

  • Uses createRequire(import.meta.url) anchored at this package's own location, then require.resolve('node-pty/package.json') to find node-pty regardless of layout.
  • chmod 0755 the platform-specific spawn-helper (Linux + macOS).
  • No-op on Windows.
  • Silently skips when there's no prebuild for the current arch (acceptable: source builds via node-gyp produce executable binaries directly).
  • ESM (matches "type": "module" in package.json).

Also added scripts/ to the published files array — without this, the script doesn't end up in the tarball.

Validation

End-to-end tested locally:

  1. npm pack to produce a tarball
  2. Installed into a fresh pnpm scratch project with enableGlobalVirtualStore: true
  3. Verified find ~/Library/pnpm -name spawn-helper | xargs ls -l shows 0755
  4. Verified Session.spawn('echo', ['hi']) works without posix_spawnp failed
  5. Confirmed the old shell workaround silently no-ops in the same layout (chmod exits 1, swallowed by || true)

Future

Once a new node-pty release ships with the upstream chmod fix (microsoft/node-pty#858, #866), this scripts/postinstall.js can be deleted entirely — @myobie/pty won't need its own workaround anymore.

Context

I'm building @overeng/pty-effect, an Effect-native wrapper around @myobie/pty, and discovered this while validating against my test suite. Related upstream conversations:


Filed on behalf of @schickling.

The previous shell-based postinstall used a relative path
(`node_modules/node-pty/prebuilds/*/spawn-helper`) that only resolves in
flat node_modules layouts. Under pnpm's content-addressed store
(especially with enableGlobalVirtualStore), node-pty is a sibling link
and the chmod silently no-ops via `2>/dev/null || true`. The result is
a non-executable spawn-helper and `posix_spawnp failed` at runtime.

Replace with a node script using createRequire from this package's own
package.json so it finds node-pty regardless of layout. Also add
`scripts/postinstall.js` to the published `files` so the script ships
in the tarball.

The fundamental fix belongs in microsoft/node-pty (PR filed separately);
this is the wrapper-side workaround until that lands.

Refs compoundingtech#6 compoundingtech#7
@myobie
myobie merged commit fbefdfa into compoundingtech:main Apr 7, 2026
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.

2 participants