Codeman logocodeman GitHub →

Fix node-pty Error: posix_spawnp failed on macOS (spawn-helper)

Updated 2026-09-25

On this pageConfirm that this is your problemWhy it happensWhy chmod-ing build/Release does not helpKeep it fixedElectron appsIf chmod did not fix itA note on Codeman

On macOS, node-pty starts every pseudo-terminal through a small helper binary called spawn-helper. node-pty 1.1.0, the current latest release on npm, publishes the prebuilt helper in prebuilds/darwin-<arch>/ without the executable bit, so the spawn fails and node-pty throws Error: posix_spawnp failed. One chmod fixes it:

chmod +x node_modules/node-pty/prebuilds/darwin-*/spawn-helper

Confirm that this is your problem

Look at the helper's mode:

ls -l node_modules/node-pty/prebuilds/darwin-*/spawn-helper

-rw-r--r-- means this bug. You can see it in the published package itself: in the node-pty@1.1.0 tarball, both prebuilds/darwin-arm64/spawn-helper and prebuilds/darwin-x64/spawn-helper are stored with mode 0644.

npm pack node-pty@1.1.0 && tar -tvzf node-pty-1.1.0.tgz | grep spawn-helper

A one-line reproduction that fails on an affected install and prints ok on a fixed one:

node -e "require('node-pty').spawn('/bin/echo', ['ok'], {}).onData(d => process.stdout.write(d))"

Why it happens

On macOS, node-pty's native code (the #if defined(__APPLE__) branch of src/unix/pty.cc) does not spawn your command directly. It calls posix_spawn on spawn-helper, passing your working directory and command as arguments, and the helper then changes directory and execvps the real command. If spawning the helper itself fails, for example because the file is not executable, the native code throws posix_spawnp failed.

Whether the missing bit reaches your disk depends on how your package manager extracts the tarball. The upstream report, node-pty#850, describes it with pnpm; Codeman's users hit it with plain npm installs on Apple Silicon.

Linux never shows the bug. node-pty ships prebuilds only for macOS and Windows, so on Linux it compiles from source with node-gyp, which produces an executable helper, and the helper is only used on macOS anyway. That asymmetry is why the bug survives: CI on a Linux box looks fine.

Why chmod-ing build/Release does not help

A common fix online runs chmod +x node_modules/node-pty/build/Release/spawn-helper. On macOS with prebuilds that directory usually does not exist. node-pty's loader (lib/utils.js) tries build/Release, then build/Debug, then prebuilds/<platform>-<arch>, and lib/unixTerminal.js takes the helper from whichever directory the native module actually loaded from. Fix the one under prebuilds/, or all of them.

Keep it fixed

A postinstall script. In your own package.json:

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

The || true keeps installs on Linux and Windows from failing when the glob matches nothing.

A runtime guard, for installs that ran with --ignore-scripts or were copied without file modes. Call it once before the first pty.spawn():

const fs = require('node:fs');
const path = require('node:path');

function ensureSpawnHelper() {
  if (process.platform !== 'darwin') return;
  const root = path.dirname(require.resolve('node-pty/package.json'))
    .replace('app.asar' + path.sep, 'app.asar.unpacked' + path.sep); // Electron
  for (const dir of ['build/Release', 'build/Debug', `prebuilds/darwin-${process.arch}`]) {
    try {
      fs.chmodSync(path.join(root, dir, 'spawn-helper'), 0o755);
    } catch {
      // not present in this layout
    }
  }
}

Upgrade. Starting with node-pty@1.2.0-beta.4, the published tarballs store both macOS helpers as -rwxr-xr-x. Those releases are on the beta dist-tag (npm install node-pty@beta); latest is still 1.1.0 at the time of writing.

Rebuild from source, carefully. Compiling needs the Xcode Command Line Tools. node-pty's install script also deletes the entire prebuilds directory as soon as npm_config_build_from_source is true, before node-gyp runs, so a failed compile leaves you with no working binary at all. The prebuilt binary is fine; only its file mode is wrong, so chmod is the better fix.

Electron apps

If you package with Electron, node-pty rewrites the helper path by replacing the first app.asar in it with app.asar.unpacked. That has two consequences:

If chmod did not fix it

Reading node-pty 1.1.0's source, the same message covers any failure before or during the helper spawn: a missing helper file or a wrong path (as in the Electron case above), and failures to allocate the pseudo-terminal itself, which run before the spawn and are reported the same way.

A wrong working directory or a command that does not exist does not produce this error on macOS. The helper handles those after it has started and exits with status 1, so you see a process that exits immediately instead. If that is your symptom, check the cwd and the command path.

A note on Codeman

Codeman, a dashboard that runs AI coding agents in tmux and uses node-pty for its terminals, shipped with this bug for a while: on a stock macOS install every session failed to start with posix_spawnp failed (Codeman issues #6 and #204). It now handles it in two places. Its postinstall chmods every helper and then proves the result by opening a real PTY, and at runtime every pty.spawn() goes through a wrapper that chmods the helper and retries once on a posix_spawnp or spawn-helper error. On a git clone, npm run fix:node-pty runs the repair by hand. See Troubleshooting.