Hooks fail with misleading ENOENT when session CWD no longer exists (Windows)
Bug Description
When the session's working directory (CWD) no longer exists at the time hooks execute (e.g., after git worktree cleanup, drive disconnection, or directory deletion), all hooks fail with a misleading ENOENT error that points to bash.exe rather than the actual missing directory.
Environment
- Claude Code version: 2.1.50
- Platform: Windows 10 (MSYS_NT-10.0-26200 / Git Bash)
- Node.js: v22.17.1
- Shell: bash (MSYS2)
Observed Behavior
Both Stop hooks (from plugins) and SessionEnd hooks fail with:
SessionEnd hook [/c/Users/Pro/.claude/hooks/session-end-copy-log.sh] failed:
Error occurred while executing hook command: ENOENT: no such file or directory,
uv_spawn 'C:\Program Files\Git\bin\bash.exe'
After session exit, the terminal reports:
Unable to find the current directory, falling back to C:\Users\Pro
Root Cause Analysis
The error is not about bash.exe being missing. Node.js uv_spawn returns ENOENT when the CWD doesn't exist, but the error message misleadingly shows the executable path.
Proof via Node.js reproduction
const { spawnSync } = require('child_process');
// ✅ Valid CWD → works fine
spawnSync('C:/Program Files/Git/bin/bash.exe', ['-c', 'echo hello'],
{ cwd: 'C:/Users/Pro' });
// → status: 0, stdout: "hello"
// ❌ Invalid CWD → ENOENT pointing at bash.exe (misleading!)
spawnSync('C:/Program Files/Git/bin/bash.exe', ['-c', 'echo hello'],
{ cwd: 'Z:/nonexistent_dir' });
// → ENOENT: spawnSync C:/Program Files/Git/bin/bash.exe ENOENT
// ❌ Same with shell:true — all spawn methods fail
spawnSync('echo hello', [],
{ shell: true, cwd: 'Z:/nonexistent_dir' });
// → ENOENT
This confirms the issue is at the OS/libuv level — any executable spawn fails when the CWD doesn't exist.
Expected Behavior
When the session's CWD no longer exists, Claude Code should:
- Detect the invalid CWD before executing hooks
- Fall back to a known-valid directory (e.g.,
os.homedir()oros.tmpdir()) - Still execute the hooks successfully
Steps to Reproduce
- Start a Claude Code session in a temporary directory (e.g., a git worktree)
- Configure any hook (Stop, SessionEnd, etc.) in
settings.json - During the session, delete or make the working directory inaccessible from another terminal
- Exit the session with
/exit - Both Stop and SessionEnd hooks fail with ENOENT
Impact
- All hooks are affected — Stop, SessionEnd, and likely others
- Affects any Windows user whose session CWD becomes invalid (worktree cleanup, drive disconnect, directory deletion)
- The misleading error message makes debugging very difficult (appears to be a missing
bash.exeissue when it's actually a CWD issue) - Plugin hooks (e.g., ralph-loop Stop hook) also fail and cannot be fixed by users
Suggested Fix
In the hook execution code, before spawning the hook command:
const cwd = fs.existsSync(sessionCwd) ? sessionCwd : os.homedir();
spawn(command, args, { cwd, ... });
Related Issues
- #24710 - Windows environment variable syntax in hooks (different issue)
- #26759 - Windows backslash paths in hooks (different issue, fixed)
Labels
bug, platform:windows, area:hooks
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗