Hook execution fails with ENOENT when CWD no longer exists (posix_spawn)
Description
When the current working directory (CWD) of a Claude Code session no longer exists at the time Stop or SessionEnd hooks execute, all hooks fail with:
ENOENT: no such file or directory, posix_spawn '/bin/sh'
The error is misleading — /bin/sh exists. The real cause is that macOS posix_spawn returns ENOENT when the process's CWD is an invalid/deleted directory.
Steps to Reproduce
- Start a Claude Code session in a temporary directory (e.g., a git worktree)
- While the session is active, delete the directory from another terminal
- End the session with
/exitor let it trigger Stop hooks
All Stop and SessionEnd hooks fail with the ENOENT error above.
Expected Behavior
Hooks should still execute even if CWD no longer exists. The hook runner should set a valid fallback CWD (e.g., { cwd: '/' } or { cwd: os.homedir() }) when spawning hook processes.
Actual Behavior
All hooks fail because posix_spawn('/bin/sh', ...) inherits the invalid CWD from the parent process and returns ENOENT.
Note: cd / && ... prefixes in hook commands don't help because /bin/sh itself fails to spawn before any shell command can execute.
Environment
- Claude Code: 2.1.62
- macOS: Darwin 25.2.0 (Sequoia)
- Architecture: arm64 (Apple Silicon M1 Max)
Proposed Fix
In the hook execution code (likely child_process.spawn or posix_spawn wrapper), add a CWD fallback:
const cwd = fs.existsSync(process.cwd()) ? process.cwd() : os.homedir();
spawn('/bin/sh', ['-c', command], { cwd, ... });
This is a minimal, safe change that only affects hook execution when the original CWD is invalid.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗