EnterWorktree does not propagate to PreToolUse hook subprocesses — hooks keep resolving against the original repo directory, not the worktree
Title
EnterWorktree does not propagate to PreToolUse hook subprocesses — hooks keep resolving against the original repo directory, not the worktree
Summary
After calling EnterWorktree to switch a session into a new git worktree, all file-editing tools (Read, Write, Edit, Bash) correctly operate against the new worktree — git rev-parse --show-toplevel inside a Bash call returns the worktree path, and relative paths resolve there. However, PreToolUse hook subprocesses configured in .claude/settings.json continue to run against the original project directory (the one the session started in, before EnterWorktree was ever called) for the rest of the session. This breaks any hook whose logic depends on ${CLAUDE_PROJECT_DIR:-$PWD} reflecting "where is this session currently working," which is a reasonable and documented assumption (see e.g. Anthropic's own guidance that CLAUDE_PROJECT_DIR is available to hooks as the project root).
Concretely, a hook file that lives inside the worktree (tracked by git, identical content to the main repo's copy at the moment EnterWorktree ran) never executes — the hook launcher instead resolves and runs the main repo's copy of the same file, using the main repo as the effective project root, indefinitely, for the rest of the session.
Environment
- OS: Windows 11 Pro 10.0.26200
- Shell: PowerShell 5.1 primary; Bash tool (Git Bash) also available
- Claude Code running as a CLI/agent SDK session (harness details not otherwise known to me)
- Repo: a pnpm workspace monorepo with
.claude/hooks/*.shPreToolUse hooks configured in.claude/settings.json
Reproduction steps
- Start a session in a git repo whose
.claude/settings.jsonregisters aPreToolUsehook forEdit|Write, where the hook script does something like:
``bash``
project_root=$(git -C "${CLAUDE_PROJECT_DIR:-$PWD}" rev-parse --show-toplevel 2>/dev/null)
- Call
EnterWorktree(withname: "some-branch") to create and switch into a new worktree, e.g..claude/worktrees/some-branch/. - Confirm the switch is real: run
git rev-parse --show-toplevelvia theBashtool. It correctly prints the new worktree's path. - Attempt any
WriteorEdittool call targeting a file inside the worktree. - Observe the hook's output/behavior (in our case, a hook that blocks the edit and reports what it thinks
project_rootis).
Expected behavior
The hook subprocess should see an environment (CLAUDE_PROJECT_DIR or equivalent) that reflects the session's current working location — i.e., the worktree entered via EnterWorktree — matching what Bash/Read/Write/Edit already correctly resolve to.
Actual behavior
The hook subprocess resolves ${CLAUDE_PROJECT_DIR:-$PWD} to the original repo directory the session started in, not the worktree. This was confirmed two independent ways:
- The hook's own error message reported the wrong root. Our hook (
worktree-guard.sh) computesproject_rootthis way and compares it against the git-toplevel of the file being edited. AfterEnterWorktreeinto.claude/worktrees/feat+some-branch, every edit attempt was blocked with:
> BLOCKED: '<path-in-worktree>' resolves to worktree '<worktree-path>', but this session is bound to '<original-main-repo-path>'.
This happened consistently across two separate EnterWorktree calls, in two different worktrees, both times reporting the exact same (wrong, original) directory as "the session."
- Direct instrumentation proved the worktree's own copy of the hook never runs. We added a debug line to the worktree's copy of the hook script (git-tracked, identical to the main repo's copy at that point) that appends a timestamped line to a log file on every invocation. We then triggered a
Writecall inside the worktree. The log file was never created — meaning the worktree's copy of the hook script was never executed at all. We repeated this with an unambiguous absolute path for the log file (ruling out a/tmpmapping difference between shells) with the same result: no log entry, ever.
The only explanation consistent with both observations: the hook launcher command in .claude/settings.json —
``json`
"command": "bash \"${CLAUDE_PROJECT_DIR:-$PWD}/.claude/hooks/worktree-guard.sh\""
${CLAUDE_PROJECT_DIR:-$PWD}` against the original (main repo) directory, so it launches and executes the main repo's copy of the script, not the worktree's, regardless of which worktree the session is actually operating in.
— itself resolves
Impact
Any PreToolUse/PostToolUse hook that relies on CLAUDE_PROJECT_DIR (or an unset fallback to the spawning process's PWD) to reason about "where is this session's work happening" is silently wrong for the entire remainder of any session that calls EnterWorktree. In our case this manifested as a hook that's supposed to prevent edits from leaking into the wrong worktree instead permanently blocking all Edit/Write calls inside every worktree entered via EnterWorktree — the opposite of its intent. We worked around it by writing files via the Bash/PowerShell tools instead (not covered by the Edit|Write hook matcher), but that's a workaround, not a fix, and it also means any hook meant to run on Bash-based writes is silently bypassed too.
We could not find any way to fix this from within the repository: editing the worktree's copy of the hook script is provably a no-op (proven not to execute), and editing the main repo's copy in place is not a real fix — it's an uncommitted local change outside git that a fresh clone or new worktree would never pick up, and it doesn't reach the team through the normal PR/commit process.
Suggested fix direction
When EnterWorktree switches a session's working context, the harness should also update whatever environment/context is used to spawn PreToolUse/PostToolUse hook subprocesses (in particular CLAUDE_PROJECT_DIR, and re-resolve the hook command's own path, which currently seems to be computed once against the original project directory) so hooks observe the same "current location" that Bash, Read, Write, Edit, and Grep already correctly do after the switch.
Reporter context
This surfaced while working on an unrelated feature task in a pnpm monorepo, using EnterWorktree/ExitWorktree for isolation as directed by the repo's own worktree conventions. Happy to provide the exact hook script, settings.json hook config, or additional repro detail if useful.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗