[BUG] WorktreeCreate / Remove hooks not called in Claude Desktop

Open 💬 24 comments Opened Mar 1, 2026 by clayallsopp
💡 Likely answer: A maintainer (amorriscode, contributor) responded on this thread — see the highlighted reply below.

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?

  • My project has WorktreeCreate and WorktreeRemove hooks setup that change where Git worktrees are managed
  • When I create a worktree via Claude Desktop, it is still creating them in <project>/.claude/worktrees, and not using the Hooks
  • I also have log files managed by my hooks, and their absence confirms they are not being called by Claude Desktop. The SessionStart hook seems to be called, but not the worktree ones.

What Should Happen?

  • WorktreeCreate/Remove should be called by Claude Desktop.

Error Messages/Logs

Steps to Reproduce

  1. Check out https://github.com/clayallsopp/worktree-desktop-repro
  2. run claude -w worktree-test
  3. observe the worktree is created in ./custom-worktrees/worktree-test
  4. observe log entry in ./logs
  5. Open that repo in Claude Desktop
  6. Ask a question about the with worktree enabled
  7. Observe worktree created in .claude/worktrees
  8. Observe no new log entry in ./logs

Claude Model

Opus

Is this a regression?

No, this never worked

Last Working Version

_No response_

Claude Code Version

2.1.52

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

Terminal.app (macOS)

Additional Information

_No response_

View original on GitHub ↗

24 Comments

sstklen · 4 months ago

Spent some time digging into this. Here's what I found:

What's happening: WorktreeCreate and WorktreeRemove hooks are only invoked by the Claude Code CLI (claude -w), not by Claude Desktop. The SessionStart hook fires in both environments, confirming Desktop does process hooks — but the worktree-specific lifecycle hooks are skipped. This affects any project relying on custom worktree management via hooks when used through Claude Desktop.

📊 _We found 5 similar cases in our knowledge base with the same pattern — this gives us high confidence in this analysis._

Hope this helps! Let me know if it doesn't match your case — happy to dig deeper. 🦞

_Disclosure: This analysis is from Confucius Debug, an AI-powered community KB for agent bugs. Please verify before applying._

---
<sub>🦞 Confucius Debug — community knowledge base for AI agent bugs. Free to search via MCP.</sub>

skyksandr · 4 months ago

Reproducible on my end too. Happy to assist on debugging/testing if needed

Aryan-Poonacha · 4 months ago

Also reproducible on mine, I just did SessionStart cleanup hook to clean up old sessions when starting a new one instead

mctrafik · 3 months ago

This is hindering parallel development on my team because only the App has preview integration and that's vital for what we're working on, but we can't run more than one at a time.

yurukusa · 3 months ago

Workaround: use PreToolUse on Agent with worktree isolation to trigger your lifecycle hooks:

INPUT=$(cat)
ISOLATION=$(echo "$INPUT" | jq -r '.tool_input.isolation // empty')
[ "$ISOLATION" = "worktree" ] || exit 0
echo "[WorktreeShim] Agent launching with worktree isolation" >&2
exit 0
COUNTER="/tmp/cc-wt-check"
COUNT=$(cat "$COUNTER" 2>/dev/null || echo 0)
COUNT=$((COUNT + 1))
echo "$COUNT" > "$COUNTER"
[ $((COUNT % 20)) -ne 0 ] && exit 0
PRUNED=$(git worktree prune --dry-run 2>/dev/null)
if [ -n "$PRUNED" ]; then
    git worktree prune 2>/dev/null
    echo "[WorktreeShim] Pruned stale worktrees" >&2
fi
exit 0

This works in Claude Desktop because PreToolUse/PostToolUse hooks fire normally — it's only the dedicated WorktreeCreate/WorktreeRemove events that are missing.

mctrafik · 3 months ago
Workaround: use PreToolUse on Agent with worktree isolation to trigger your lifecycle hooks: INPUT=$(cat) ISOLATION=$(echo "$INPUT" | jq -r '.tool_input.isolation // empty') [ "$ISOLATION" = "worktree" ] || exit 0 echo "[WorktreeShim] Agent launching with worktree isolation" >&2 exit 0 COUNTER="/tmp/cc-wt-check" COUNT=$(cat "$COUNTER" 2>/dev/null || echo 0) COUNT=$((COUNT + 1)) echo "$COUNT" > "$COUNTER" [ $((COUNT % 20)) -ne 0 ] && exit 0 PRUNED=$(git worktree prune --dry-run 2>/dev/null) if [ -n "$PRUNED" ]; then git worktree prune 2>/dev/null echo "[WorktreeShim] Pruned stale worktrees" >&2 fi exit 0 This works in Claude Desktop because PreToolUse/PostToolUse hooks fire normally — it's only the dedicated WorktreeCreate/WorktreeRemove events that are missing.

How does this let you move where the worktree is created?!

skyksandr · 3 months ago

I am using SessionStart instead with a git-ignored file as marker:

# ── Resolve repo root and check if we're in a worktree ──
repo_root=$(git rev-parse --show-toplevel 2>/dev/null) || exit 0
is_worktree=$(git rev-parse --is-inside-work-tree 2>/dev/null)
common_dir=$(git rev-parse --git-common-dir 2>/dev/null)
git_dir=$(git rev-parse --git-dir 2>/dev/null)

# If git-dir equals git-common-dir, we're in the main repo — not a worktree
if [[ "$is_worktree" != "true" ]] || [[ "$(realpath "$git_dir")" == "$(realpath "$common_dir")" ]]; then
    exit 0
fi

# ── We're in a worktree — check if it needs initialization ──
# Use doppler.yaml as the marker since Procfile.local is now shared via git
if [[ ! -f "$repo_root/doppler.yaml" ]]; then
  # YOUR INIT WORKTREE SCRIPT GOES HERE
fi
KingMob · 3 months ago

This also interferes with the use of jj. I have custom worktree hooks that create jj workspaces, which Claude Desktop completely ignores in Claude 1.2581.0 (f10398)

thomast8 · 3 months ago

Still repros in the Desktop app. Worth flagging the doc contradiction too: the hooks reference says WorktreeCreate 'replaces Claude Code's default git worktree logic entirely', but the EnterWorktree tool description explicitly says it only delegates to the hook outside a git repo. One of the two docs is wrong, and this issue is the fallout.

alex-m-peters · 3 months ago

Just confirmed that the redesigned desktop app released on 2026-04-14 still is not triggering WorktreeCreate when using worktrees in the Claude Code tab.

pawel-omniaz · 3 months ago

Please fix this.

grempe · 2 months ago

Reproduces here as well, with an additional wrinkle: my project is a jj (Jujutsu) repo with a colocated .git/. The WorktreeCreate hook invokes jj workspace add to create a real jj workspace alongside the git checkout. CLI claude --worktree <name> honors the hook correctly. Desktop's UI worktree toggle bypasses it and falls back to git worktree add, which silently "works" because of the colocated .git/ — but the new worktree has no .jj/ of its own, so any jj commands inside it walk up to the parent project's state and operate on the wrong workspace. This breaks any jj-aware tooling, slash commands, or skills inside the Desktop-created worktree.

Worth calling out because in non-colocated jj-only repos this would fail loudly (no .git/ for git worktree add to use). With colocated repos it fails silently, which is harder to debug. The hook should be honored regardless of whether .git/ is present.

Environment:

  • Claude Desktop: Claude 1.4758.0 (fb266c) 2026-04-24T20:22:30.000Z
  • macOS (M-series)
  • jj 0.40.0
  • Hook scripts and .claude/settings.json registration verified working with CLI claude --worktree <name> (creates claude-<name> jj workspace as expected); Desktop UI toggle on the same project bypasses the hook entirely.

Reproduction signal: from inside a Desktop-created worktree, jj workspace list only shows default, jj root resolves to the parent project, and jj status reports paths like ../../../CLAUDE.md — i.e., jj is operating on the parent's working copy, not the worktree's.

amorriscode contributor · 2 months ago

Sorry folks, hadn't seen this issue before. Fixing it up.

ralphjsmit · 1 month ago

@amorriscode Sorry for the ping, any clue when the fix for the worktree-hooks from the Claude Desktop app can be released? (Then I know whether to work-around or wait a couple of days.) Thanks for the effort anyway 🙌

amorriscode contributor · 1 month ago

@ralphjsmit sorry, this one slipped from my plate. I'll have it in for Tuesday's release.

ralphjsmit · 1 month ago

Thanks so much!

ericwu917 · 1 month ago

Still repros on Claude Desktop 1.9255.0 (macOS).

Earlier in this thread (May 21) @amorriscode said a fix was coming "in Tuesday's release" — that's now 6 days ago and the issue is still open. Public CLI releases v2.1.146 → v2.1.152 don't mention a Desktop dispatch fix in any changelog.

Fresh repro ~30 minutes ago:

  • Toggled Worktree on a session in Desktop 1.9255.0.
  • Desktop wrote a new entry to ~/Library/Application Support/Claude/git-worktrees.json at 2026-05-27T09:56:28Z:

``
name: dreamy-matsumoto-769b26
branch: claude/dreamy-matsumoto-769b26
path: <repo>/.claude/worktrees/dreamy-matsumoto-769b26
`
→ path / branch / name are all Desktop's hard-coded
claude/<codename>` convention.

  • My WorktreeCreate hook (custom branch naming feat/YYMMDD-<rest>, custom path layout) was not invoked~/.claude/worktree-hook.log mtime didn't move; last entry is 2026-05-24T21:46:01 from a CLI claude -w invocation.
  • CLI claude -w <name> still fires the hook correctly — bug is purely on the Desktop dispatch path, as originally filed.

@amorriscode any update on landing the fix? Happy to test a build.

christopher-buss · 1 month ago

This seems to work for me locally - what doesn't work is an external plugin can't have it fire. For example, https://worktrunk.dev/ has its own WorktreeCreate - but via the plugin it wont work, even though a local WorktreeCreate / WorktreeRemove will.

pawel-omniaz · 1 month ago

@amorriscode Hi, was it fixed and released? Because I see this ticket is still open and I'm not sure if I can safely migrate from SessionStart hook.

etiennecrb · 1 month ago

For what it's worth: it's been working for me for a week or so. (Although the agent often tries to read files from the main worktree which is annoying in plan mode but it is tracked in a separate issue)

pawel-omniaz · 1 month ago

@etiennecrb Thanks for info. Yeah, I have exactly same issue! Do you have url of existing ticket for this?

eman2673 · 1 month ago

This is still quite broken for me. To be clear, I'm using desktop SSH sessions.

Works: Session can spawn an agent in a new worktree and hooks will fire
Fails

  • Session suggests a task -> Click through -> new session that is spawned doesn't fire the hooks
  • Manual start a session -> Hooks will not fire
agoodkind · 16 days ago

This still constantly fires for me on the latest version

!image

sp-kjablonski · 9 hours ago

Re-tested today on Claude Code 2.1.204 (macOS, Claude Desktop). Current status is split:

Session worktrees: still broken. When Claude Desktop creates (or reuses) a worktree for a session, it goes through its own pool mechanism and never calls the WorktreeCreate/WorktreeRemove hooks. ~/Library/Logs/Claude/main.log shows:

[rebindWorktree] Rebound <repo>/.claude/worktrees/<name> (was leased by none) to local_... on branch claude/...
[WorktreePool] Reused worktree <name> for session local_... (was leased by none)

and when no pooled worktree is available:

[createWorktree] ... git worktree add --no-checkout <repo>/.claude/worktrees/<name> ...

There is no WorktreeCreate mention anywhere in the log, the worktree lands in the default .claude/worktrees/ location, and none of the side effects of our hook (worktrunk-managed path, pre-start setup files) are present. The pool also explains why worktree directory names don't match the session branch - directories get re-leased and rebound to new branches.

Agent tool with isolation: "worktree" (in-session subagents): the hook IS called now. A subagent spawned with worktree isolation fails with WorktreeCreate hook failed: ... when the hook command errors, which proves invocation. So the gap appears specific to the Desktop app's session-level WorktreePool path, not to the hook plumbing in general.

Setup for reference: hooks come from the worktrunk plugin (https://worktrunk.dev/claude-code/), which registers WorktreeCreate/WorktreeRemove in its hooks.json; other plugin hooks (UserPromptSubmit, SessionEnd) fire fine in the same sessions.