Bug: Stop hooks run with CWD set to .claude/helpers/ instead of project root
Description
Stop hooks execute with the current working directory set to .claude/helpers/ instead of the project root. This causes any relative paths in hook commands to resolve incorrectly.
Reproduction
- Create a hook in
.claude/settings.json:
{
"hooks": {
"Stop": [{
"hooks": [{
"type": "command",
"command": "node .claude/helpers/my-hook.mjs sync",
"timeout": 8000,
"continueOnError": true
}]
}]
}
}
- Place
my-hook.mjsat.claude/helpers/my-hook.mjs
- End a session — the Stop hook fails with
MODULE_NOT_FOUNDbecause it resolves to.claude/helpers/.claude/helpers/my-hook.mjs(doubled path).
Expected Behavior
Stop hooks should run with CWD set to the project root (same as SessionStart hooks), so that relative paths like .claude/helpers/my-hook.mjs resolve correctly.
Actual Behavior
Stop hooks run with CWD = .claude/helpers/, causing relative paths to double:
- Expected:
<project-root>/.claude/helpers/my-hook.mjs - Actual:
<project-root>/.claude/helpers/.claude/helpers/my-hook.mjs
Verification
# From project root — works
ls .claude/helpers/my-hook.mjs # ✓ found
# From .claude/helpers/ (what Stop hooks see) — fails
cd .claude/helpers/
ls .claude/helpers/my-hook.mjs # ✗ No such file or directory
Workaround
Use git rev-parse --show-toplevel to resolve the project root:
"command": "node \"$(git rev-parse --show-toplevel 2>/dev/null || echo .)/.claude/helpers/my-hook.mjs\" sync"
Impact
This affects any Stop hook that uses relative paths from the project root. The claude-flow init wizard generates stock settings.json with node .claude/helpers/auto-memory-hook.mjs sync in the Stop hook — which silently fails on every session end due to this bug (continueOnError: true masks the failure).
SessionStart hooks are not affected — they run with the correct CWD (project root).
Environment
- Claude Code: v2.1.39
- Platform: Linux 6.8.0-90-generic (x86_64)
- Node.js: v24.13.0
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗