Hooks fail with "posix_spawn '/bin/sh' ENOENT" when the session cwd is deleted (worktree cleanup); spawn should fall back to an existing cwd

Resolved 💬 2 comments Opened Jun 4, 2026 by Nickcom4 Closed Jun 8, 2026

Summary

When the session's current working directory is deleted while Claude Code is running, every subsequently-fired hook fails with:

ENOENT: no such file or directory, posix_spawn '/bin/sh'

/bin/sh exists and is executable; the error is misleading. The real cause is that hooks are spawned with cwd = <session cwd>, and when that directory no longer exists, posix_spawn fails to establish the child's working directory and reports ENOENT against the interpreter. The failure happens before the hook command runs, so nothing inside the hook script can recover from it.

Impact

Once the cwd is gone, the session is effectively bricked: PostToolUse, Stop, UserPromptSubmit, etc. all fail to spawn for the remainder of the session. In one observed case a single Stop event produced 17 identical posix_spawn '/bin/sh' ENOENT errors.

How the cwd gets deleted (common trigger)

Git worktree workflows. A session runs inside a linked worktree (e.g. .../.claude/worktrees/<name>). The worktree's PR is squash-merged, and a cleanup step (a Stop hook, a background job, a concurrent session, or git worktree prune) removes that worktree directory — which is the live session's cwd. From that point every hook spawn fails.

Reproduction

  1. Start Claude Code with cwd inside a directory D and at least one hook configured (any event).
  2. From outside the session, delete D (rm -rf D, or git worktree remove --force D).
  3. Trigger any hook (e.g. submit a prompt, or let a Stop hook fire).
  4. Observe: <event> hook error: ENOENT: no such file or directory, posix_spawn '/bin/sh' for every configured hook, repeatedly.

Expected

Hooks should still spawn. When the recorded session cwd no longer exists, Claude Code should fall back to a guaranteed-existing directory (e.g. $HOME, or the project/repo root) as the spawn cwd before invoking the hook, rather than passing a deleted path to posix_spawn.

Suggested fix

Before spawning a hook process, stat the intended cwd; if it does not resolve, substitute a fallback ($HOME/) for the child's cwd. This is a small, safe change and is the only layer that can fix this, because the hook script itself never runs when the spawn fails.

Workarounds (incomplete)

Sourcing a cd "$HOME" recovery snippet at the top of each hook does not work: the snippet only runs after the shell has spawned, and the spawn is exactly what fails. The only user-side mitigation is to avoid ever deleting a live session's cwd, which is hard to guarantee across concurrent sessions and automated cleanup.

Environment

  • Claude Code v2.1.162
  • macOS (Darwin 24.x), zsh
  • Reproducible whenever the session cwd is removed out from under a running session.

View original on GitHub ↗

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