Hooks fail on non-FHS systems (NixOS): ENOENT posix_spawn '/bin/sh'

Resolved 💬 1 comment Opened May 27, 2026 by rayhem Closed Jun 28, 2026

Summary

Hook commands fail on NixOS and other non-FHS Linux systems because the harness spawns hooks via \/bin/sh\, which does not exist on these systems.

\\\
Stop hook error: Failed with non-blocking status code: Error occurred while
executing hook command: ENOENT: no such file or directory, posix_spawn '/bin/sh'
\
\\

Environment

  • OS: NixOS (non-FHS — \/bin\ does not exist; \sh\ lives at \/nix/store/.../bin/sh\)
  • Claude Code version: latest
  • Hook type: Stop (reproduced; likely affects all hook types)

Root cause

Node.js's \child_process.exec()\ on POSIX systems unconditionally calls \posix_spawn('/bin/sh', ['-c', command], ...)\. This is fine on FHS-compliant systems but breaks anywhere \/bin/sh\ is absent, including NixOS, bubblewrap/container sandboxes without FHS bind mounts, and some minimal Docker base images.

Suggested fix

Option A — Respect \$SHELL\ (least invasive):
\\\js
const shell = process.env.SHELL || '/bin/sh';
child_process.execFile(shell, ['-c', command], ...);
\
\\

Option B — PATH-based lookup (most portable):
\\\js
// spawn with a bare name delegates resolution to execvp(), which searches PATH
child_process.spawn('sh', ['-c', command], { shell: false });
\
\\

Either option eliminates the hardcoded \/bin/sh\ assumption without requiring user configuration.

Workaround

Patch the host system to create \/bin/sh\ via a NixOS activation script. This works but forces FHS compatibility on the entire system just to accommodate this assumption in the harness.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗