Hook runner spawns with deleted session cwd — every hook fails with `ENOENT: posix_spawn '/bin/sh'` and PreToolUse deny-hooks fail open (distinct from the Bash-tool cwd validation)
Environment
- Claude Code 2.1.170 (also reproduced on 2.1.169)
- macOS 15.x (Darwin 25.4.0), arm64, zsh
- Hooks configured via project
.claude/settings.local.json, all commands using absolute"$CLAUDE_PROJECT_DIR"/...paths
Summary
When the directory backing the session's cwd is deleted while the session is still logically parked in it, every subsequent hook invocation fails with:
Failed with non-blocking status code: Error occurred while executing hook command: ENOENT: no such file or directory, posix_spawn '/bin/sh'
This continues for every hook event (Stop, UserPromptSubmit, PreToolUse, …) until something changes the session cwd (ExitWorktree / EnterWorktree / cd). /bin/sh and the hook scripts are fine — the hook runner spawns /bin/sh -c <command> with the session's (now-deleted) cwd, and macOS posix_spawn reports ENOENT against the binary rather than the missing working directory.
Why this is not the (fixed) Bash-tool issue
There are two distinct code paths that break when the cwd is deleted, and the issue tracker currently conflates them:
- Bash tool path — validates cwd before spawning the user's shell. Fixed in Feb 2026 (#21580, #26136), regression reported in #52747 (filed 2026-04-24, closed as completed the same day with no fix version named, immediately disputed in comments, then auto-locked).
- Hook runner path — #29260 reported exactly this failure and was closed as a duplicate into the Bash-tool chain. It is not a duplicate: in our 2.1.170 reproduction the session itself kept working (tools ran, ExitWorktree recovered the cwd) while every hook ENOENT'd. Whatever cwd validation the Bash tool has, the hook runner has none.
Net effect: the hook-runner bug is reproducible on the latest release but currently has no open issue tracking it.
Reproduction
- Configure any hook, e.g. a Stop hook running
echo ok. - Start
claudeand move the session into a disposable directory (e.g.EnterWorktree, or start the session inside one). - Delete that directory out from under the session — e.g. from the session itself:
git -C <repo-root> worktree remove <that-dir> --force, or externally:rm -rf <that-dir>. - End the turn and/or submit a new prompt.
Observed: the Stop hook fails with the posix_spawn '/bin/sh' ENOENT at turn end; UserPromptSubmit fails on the next prompt; PreToolUse hooks fail on subsequent tool calls. All hooks recover the moment the session cwd points at an existing directory again (in our case ExitWorktree(action: keep)).
We hit this organically three times in one session on 2026-06-10: an agent used a documented git worktree remove --force escape (a workaround for the #42282 "subagent with a cwd override" family) at three consecutive task boundaries, and each boundary produced the same Stop → UserPromptSubmit → PreToolUse ENOENT window. Transcript excerpts available on request.
Impact — this fails open, which is worse than failing
Hook execution errors are non-blocking by design, so while the cwd is dead:
- PreToolUse deny-hooks silently stop enforcing. We use PreToolUse hooks as policy gates for dangerous Bash; in the broken window every gated call proceeds unchecked, with only a small non-blocking error line as the tell.
- UserPromptSubmit context injection silently disappears.
- Stop-hook checklists/automation silently skip.
A user who doesn't recognize the misleading posix_spawn '/bin/sh' message has no idea their policy layer is offline.
Proposed fix
In the hook runner, validate the spawn cwd and fall back, e.g.:
const cwd = fs.existsSync(process.cwd()) ? process.cwd() : (projectDir ?? os.homedir());
spawn('/bin/sh', ['-c', command], { cwd, ... });
(project dir → home → / — same shape #29260 proposed). Optionally emit a one-line warning the model can see, so sessions self-heal by re-pointing their cwd. Note that cd / prefixes inside hook commands cannot help — the shell never spawns.
References
- #29260 — this exact hook-runner failure, closed as duplicate (into the Bash-tool chain)
- #52747 — Bash-tool regression report (closed as completed with no fix version, disputed, locked)
- #21580, #26136 — the Feb 2026 Bash-tool cwd fixes
- #27768 — Windows variant of the misleading hook ENOENT
- #12956 — earlier deleted-cwd crash report
- #42282 / #28017 — the cwd-override family that motivates worktree-remove escapes in agent tooling
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗