CLI hooks: process CWD drifts away from project root mid-session, causing bare-relative hook commands to resolve against the wrong directory (Windows, 2.1.113)
Summary
Claude Code CLI does not pin the hook execution CWD to the project root. In a long session where a sub-agent causes the process CWD to drift into a subdirectory (e.g. .claude/claims/ after writing a claim JSON), subsequent hook firings resolve bare relative commands like python .claude/hooks/foo.py against the drifted CWD — producing paths like .claude\claims\.claude\hooks\foo.py that don't exist. Hooks then fail with [Errno 2] No such file or directory.
Repro
- Project with
.claude/settings.jsoncontaining hook commands like:
``json``
{ "command": "python .claude/hooks/reject_deferral_phrases.py" }
- Long session on Windows, CC 2.1.113.
- Spawn a sub-agent that writes files into
.claude/claims/(e.g. via a Python helper that cd's or resolves paths into that subdirectory). - After enough sub-agent activity, the main CC process CWD drifts to
C:\<project>\.claude\claims\. - Trigger a hook — PreToolUse:Edit on any file edit, or PreCompact via
/compact. - Hook fails:
````
can't open file 'C:\<project>\.claude\claims\.claude\hooks\<script>.py': [Errno 2] No such file or directory
Observed (2026-04-19, 2.1.113, Windows)
- 17:28 PT: BEACH_LEDGER write, PreToolUse:Edit hook
reject_deferral_phrases.pyfails with doubled path. - 17:43 PT:
/loopwakeup hits context limit (separate bug — #50920 — autoCompact did not fire). - User types
/compactto recover → PreCompact hooks (pre_compact_block_check.py,pre_compact_save.py) BOTH fail with the same doubled path → compaction blocked → session dies unrecoverably.
Impact
- Hooks fail silently during normal tool use (policy hooks like deferral-phrase rejection are not enforced — unsafe).
- Catastrophic in combination with context-limit exhaustion (#50920): the
/compactrecovery workaround is unusable because PreCompact hooks fail the same way. A session that hits the context wall can't be saved. - Affects any project that follows the common
python .claude/hooks/foo.pypattern insettings.json— i.e. the form shown in most example configs.
Expected
CC should either:
- Pin the hook execution CWD to the project root (don't inherit the main process CWD, which is allowed to drift), OR
- Resolve hook command paths relative to the project root regardless of CWD, OR
- Loudly refuse to run a hook command whose first path-like argument doesn't exist at the resolved location and surface the project-root-relative suggestion.
Workaround (local)
Prefix every hook command with $CLAUDE_PROJECT_DIR/:
{ "command": "python \"$CLAUDE_PROJECT_DIR/.claude/hooks/foo.py\"" }
Works, but is a landmine for new users — the example configs and the vast majority of community setups use the bare-relative form.
Related
- #42065 (CLOSED + LOCKED) — Windows hook relative paths with
..fail intermittently. Same class of bug, slightly different trigger. Lock bot instructed re-filing. - #42687 (OPEN) — MCP servers don't receive
cwdorCLAUDE_PROJECT_DIR. Adjacent. - #50920 (OPEN) — CLI autoCompact no-fire on scheduled task wake. Orthogonal bug — but this CWD issue blocks that bug's documented workaround.
Environment
- Claude Code CLI: 2.1.113
- Platform: Windows
- Project: trading system with heavy sub-agent workflow (
ram-safety-auditorwrites claim JSONs into.claude/claims/)
Happy to run diagnostics if useful — e.g., a hook that dumps os.getcwd() + env to confirm the CWD-drift mechanism before the doubled path is generated.
This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗