Hook execution fails with ENOENT when CWD no longer exists (posix_spawn)

Resolved 💬 3 comments Opened Feb 27, 2026 by yulflow Closed Mar 2, 2026

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

  1. Start a Claude Code session in a temporary directory (e.g., a git worktree)
  2. While the session is active, delete the directory from another terminal
  3. End the session with /exit or 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.

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗