[BUG] PostToolUse hook not invoked when editing files outside project CWD (path-join bug in Go binary)
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
---
Title: PostToolUse hook not invoked when editing files outside project CWD (path-join bug in Go
binary)
Version: 2.1.183 (macOS, native Mach-O x86_64)
Reproduce:
- Start Claude Code in project /Users/les/Projects/mahavishnu
- Edit any file outside that directory (e.g. /Users/les/Projects/css-mcp/css_mcp/analyzer.py)
- See: PostToolUse:Edit hook additional context: open
/Users/les/Projects/mahavishnu/Users/les/Projects/css-mcp/css_mcp/analyzer.py: no such file or
directory
Evidence the hook never runs: Added sys.stderr.write(...) as the first line after the hook reads
file_path — no output ever appeared.
Root cause (observed): Claude Code constructs the path as:
filepath.Join(cwd, strings.TrimPrefix(absoluteFilePath, "/"))
which produces <cwd>/<absolute-path-without-leading-slash> instead of using the absolute path
directly. The os.Open() of this invalid path fails with Go's *os.PathError, the hook spawn is
aborted, and the error is surfaced as "hook additional context."
Impact: PostToolUse hooks never fire for any file outside the active project directory, silently
skipping all hook side-effects (reflection storage, code indexing, etc.).
Workaround: None from user config; binary-level fix required.
---
What Should Happen?
see above
Error Messages/Logs
Steps to Reproduce
see above
Claude Model
Sonnet (default)
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.1.183
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
iTerm2
Additional Information
_No response_
3 Comments
Confirming this is still present on 2.1.205 (macOS), and adding two repro cases the original report doesn't cover — same root cause (
filepath.Join(cwd, strings.TrimPrefix(absoluteFilePath, "/"))), broader trigger surface.1. It also fires for a file _inside_ the project, when CWD is a git worktree nested under the main checkout.
The title frames this as "files _outside_ project CWD", but the doubled-path bug also hits files that are genuinely inside the working directory when there's a project-root vs. worktree-root mismatch. Setup:
/Users/me/proj/myrepo(holds the real.gitdirectory)/Users/me/proj/myrepo/.worktrees/wt-1(its.gitis a file:gitdir: …/myrepo/.git/worktrees/wt-1)Editing
.../wt-1/.github/workflows/foo.yml(a file inside the worktree/CWD) produces:The worktree is nested under the main checkout, so the project-root walk finds the main repo's
.gitwhile the file's absolute path is under the worktree — the sameTrimPrefix+Joinbranch fires and doubles the path. A non-nested, non-worktree checkout (where cwd == project root) sidesteps it, which is probably why it reads as "outside CWD only".2. Same bug surfaces via the built-in dependency/lockfile scanner (
PreToolUse:Bash), not justPostToolUse:Edit.With no
hooksconfigured anywhere, Bash commands still emit:for lockfiles the built-in scanner probes (
package-lock.json,Gemfile.lock, etc.). Same message shape, same doubled path, different built-in caller — so the fix atfilepath.Join(cwd, TrimPrefix(absPath,"/"))would clear both surfaces at once.Impact confirmation: cosmetic in the sense that the Edit/Bash still succeeds, but the hook's/scanner's side-effect is silently skipped for that path, and the ENOENT string gets injected into the model's context as "additional context" on every such call — noisy across a long session.
Suggested guard: only
Join(cwd, x)when!filepath.IsAbs(x); otherwise use the absolute path directly (orfilepath.Rel(projectRoot, absPath)if a project-relative path is actually needed).Tried to reproduce on 2.1.233 (Linux): registered a
PostToolUsehook with anEditmatcher, then had Claude edit a file outside the project directory. The hook fired, the JSON on its stdin contained the correct absolutefile_path(not joined with the project directory), and the edit applied — so Claude Code is passing the path through unmodified, and I couldn't find any release between 2.1.183 and now that changed this.Two observations that point at the hook command itself rather than Claude Code:
PostToolUse:Edit hook additional context: open …: no such file or directoryis Claude Code relaying your hook's own output. That error string is the format a Go program produces — Claude Code isn't written in Go. Hooks run with their working directory set to the project directory, so if any Go binary in your hook chain joins its cwd with the (already absolute)file_path, you get exactly the doubled path you saw.sys.stderr.write(...)not appearing isn't evidence the hook didn't run: hook stderr (on exit code 0) goes to the debug log only, not the transcript. Check withclaude --debug.Could you check the hook command chain (especially any Go wrapper/launcher) for path joining, and use
file_pathfrom stdin as-is? Docs: https://code.claude.com/docs/en/hooks. If you still hit this withfile_pathused verbatim, please share the hook config from your settings and we'll take another look.🤖 Generated with Claude Code
This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.