EnterWorktree does not propagate to PreToolUse hook subprocesses — hooks keep resolving against the original repo directory, not the worktree

Status Open
Maintainer reply None cached
Activity 1 comment · opened Aug 19, 2026

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/*.sh PreToolUse hooks configured in .claude/settings.json

Reproduction steps

  1. Start a session in a git repo whose .claude/settings.json registers a PreToolUse hook for Edit|Write, where the hook script does something like:

``bash
project_root=$(git -C "${CLAUDE_PROJECT_DIR:-$PWD}" rev-parse --show-toplevel 2>/dev/null)
``

  1. Call EnterWorktree (with name: "some-branch") to create and switch into a new worktree, e.g. .claude/worktrees/some-branch/.
  2. Confirm the switch is real: run git rev-parse --show-toplevel via the Bash tool. It correctly prints the new worktree's path.
  3. Attempt any Write or Edit tool call targeting a file inside the worktree.
  4. Observe the hook's output/behavior (in our case, a hook that blocks the edit and reports what it thinks project_root is).

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:

  1. The hook's own error message reported the wrong root. Our hook (worktree-guard.sh) computes project_root this way and compares it against the git-toplevel of the file being edited. After EnterWorktree into .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."

  1. 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 Write call 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 /tmp mapping 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\""
`
— itself resolves
${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.

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.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗