[BUG] WorktreeCreate/WorktreeRemove hooks from multiple settings levels cause race conditions

Resolved 💬 5 comments Opened Mar 25, 2026 by sti0 Closed May 1, 2026

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?

Summary

When WorktreeCreate (or WorktreeRemove) hooks are defined at both the user level (~/.claude/settings.json) and the project level (.claude/settings.json), both hooks execute in parallel. This causes a race condition where the faster (simpler) user-level hook wins, and the project-level hook sees the worktree as already created — silently bypassing the project's custom worktree logic.

Actual Behavior

Both hooks run in parallel. The user-level hook (simpler, faster) creates the worktree first. The project-level hook then detects the branch is already checked out and exits early, returning the path created by the user-level hook — completely bypassing the project's custom logic.

Impact

  • Silent failure: No error is shown. The project hook silently falls back to the user-level result.
  • Lost functionality: Project-specific setup (environment files, dependency installation, schema configuration) is completely skipped.
  • No workaround at project level: The only fix is to remove the user-level hooks entirely, which defeats the purpose of having a generic fallback for other repositories.

What Should Happen?

Expected Behavior

Project-level hooks should either:

  1. Override user-level hooks for the same event (project-specific config takes precedence), or
  2. Run sequentially with a defined order (project after user), so the project hook can act on the result, or
  3. Provide a documented precedence/merge strategy for hooks that produce output (like WorktreeCreate which returns a directory path)

Error Messages/Logs

Silently fails.

Steps to Reproduce

Minimum Reproducible Example

1. User-level settings (~/.claude/settings.json)

{
  "hooks": {
    "WorktreeCreate": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "bash -c 'N=$(jq -r .name); C=$(jq -r .cwd); R=$(git -C \"$C\" rev-parse --show-toplevel); D=\"$R/.worktrees/$N\"; mkdir -p \"$R/.worktrees\"; { git -C \"$C\" worktree add \"$D\" -b \"$N\" || git -C \"$C\" worktree add \"$D\" \"$N\"; } >&2 && echo \"$D\"'"
          }
        ]
      }
    ]
  }
}

This creates worktrees at .worktrees/<name> (e.g. .worktrees/my-feature).

2. Project-level settings (.claude/settings.json in repo root)

{
  "hooks": {
    "WorktreeCreate": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "bash -c 'N=$(jq -r .name); R=$(git rev-parse --show-toplevel); cd \"$R\"; ID=$(ls -d .worktrees/[0-9]* 2>/dev/null | sed \"s/.*\\///;s/-.*//' | sort -n | tail -1); ID=$((${ID:-0}+1)); D=\"$R/.worktrees/$ID-$N\"; mkdir -p \"$R/.worktrees\"; git worktree add \"$D\" -b \"$N\" >&2 && echo \"$D\"'"
          }
        ]
      }
    ]
  }
}

This creates numbered worktrees at .worktrees/<id>-<name> (e.g. .worktrees/42-my-feature).

3. Reproduction steps

# Initialize test repo
mkdir /tmp/test-repo && cd /tmp/test-repo
git init && git commit --allow-empty -m "init"
mkdir -p .claude

# Create project settings with numbered worktree hook (see above)
cat > .claude/settings.json << 'EOF'
{ "hooks": { "WorktreeCreate": [{ "hooks": [{ "type": "command", "command": "bash -c 'N=$(jq -r .name); echo \"PROJECT HOOK: creating numbered worktree\" >&2; R=$(git rev-parse --show-toplevel); D=\"$R/.worktrees/1-$N\"; mkdir -p \"$R/.worktrees\"; git worktree add \"$D\" -b \"$N\" >&2 && echo \"$D\"'" }] }] } }
EOF

# Ensure user settings also define WorktreeCreate (see above)

# Launch Claude Code and create a worktree
claude -w my-feature

4. Expected result

Worktree created at .worktrees/1-my-feature (numbered, from project hook).

5. Actual result

Worktree created at .worktrees/my-feature (unnumbered, from user hook). The project hook finds the branch already checked out and exits early.

Claude Model

Not sure / Multiple models

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.81

Platform

Anthropic API

Operating System

Other Linux

Terminal/Shell

Other

Additional Information

Suggested Fix

For hooks that produce output consumed by Claude Code (specifically WorktreeCreate which returns a directory path):

  • Option A: Project-level hooks should take precedence over user-level hooks. If a project defines WorktreeCreate, the user-level WorktreeCreate should not run.
  • Option B: Allow a priority or exclusive flag so project hooks can declare they replace user-level hooks for the same event.
  • Option C: Run hooks sequentially (project after user) and let the last hook's output win — with the ability for later hooks to see the previous result.

View original on GitHub ↗

This issue has 5 comments on GitHub. Read the full discussion on GitHub ↗