Repository navigation
[Bug]: Worktrees T3 creates for Claude threads ignore Claude Code's WorktreeCreate / WorktreeRemove hooks #24645
Description
Before submitting
- I searched existing issues and did not find a duplicate.
- I included enough detail to reproduce or investigate the problem.
Area
apps/server
Steps to reproduce
- In a git repo, add a project-level Claude Code
WorktreeCreatehook in.claude/settings.json. Make it log its input, create the worktree (git worktree add …) and print the path. ASessionStarthook that logs too is useful for comparison. - Add the repo as a T3 project and start a thread on a Claude provider instance.
- In the thread, have Claude create a worktree with its own
EnterWorktreetool. → TheWorktreeCreatehook runs, and Claude uses the path it returns. - In the same thread, call
delegate_taskwithisolation: "worktree", or start a new thread in a new worktree from the UI. → TheWorktreeCreatehook is not called. T3 creates~/.t3/worktrees/<repo>/t3-<id>with plaingit worktree add. The child'sSessionStarthook does fire inside that worktree.
Expected behavior
T3 Code uses Claude Code's login and configuration (docs/user/providers-claude.md). So for Claude-backed threads, a worktree should be created and removed by the project's configured Claude Code WorktreeCreate / WorktreeRemove hooks, as Claude Code does. Claude Code runs a WorktreeCreate hook instead of git worktree add and uses the path it prints. Today, the same thread gets different worktrees depending on whether Claude or T3 creates them.
Actual behavior
T3 creates and removes worktrees itself, through GitVcsDriver.createWorktree / removeWorktree (git worktree add / remove); storage cleanup and PR review checkouts do the same. The configured hooks never run for T3-made worktrees.
runOnWorktreeCreate scripts don't solve this. They run after T3 has already created a plain git worktree at its own path, so they can't control where or how the checkout is made.
Example of a hook this breaks: a repo that needs sibling repos checked out next to it. The code refers to ../shared-lib and similar paths. The WorktreeCreate hook creates the main worktree and matching worktrees of each sibling in a parallel directory, so the relative paths resolve, and WorktreeRemove tears all of them down together. A T3-made worktree under ~/.t3/worktrees/<repo>/t3-<id> has no siblings beside it, so builds and tests in that thread fail, and nothing cleans up siblings on removal.
Impact
Major degradation or frequent failure (for any repo that depends on these hooks: T3 worktree isolation and new-worktree threads are unusable there).
Version or commit
T3 Code 0.1.2 (CLI service); behavior read against main @ 453bf01
Environment
macOS 26.2 (Apple Silicon), T3 CLI service, Claude Code 2.1.308 (claudeAgent provider)
Logs or stack traces
Hook log from the repro (one T3 thread; payloads trimmed):
01:00:58 SessionStart {"cwd":"/private/tmp/t3-hooktest/repo", ...} # parent thread starts
01:01:01 WorktreeCreate {"cwd":"/private/tmp/t3-hooktest/repo", ...} # Claude EnterWorktree → hook runs, its path is used
01:01:06 SessionStart {"cwd":"/Users/…/.t3/worktrees/repo/t3-8d562f36", ...} # delegate_task isolation:"worktree" → NO WorktreeCreate; T3 used git directlygit worktree list afterwards:
/private/tmp/t3-hooktest/repo [master]
/private/tmp/t3-hooktest/wt-native [wt-native] # created by the hook
/Users/…/.t3/worktrees/repo/t3-8d562f36 [t3/reply-child-ok] # created by T3, hook bypassedWorkaround
Avoid T3-made worktrees in these repos: ask Claude to use EnterWorktree or native subagent worktree isolation, which do run the hooks. This loses T3's worktree features (delegate_task worktree isolation, new-worktree threads, automatic cleanup).
Possible fix (suggestion only)
An opt-in per-project setting, for example worktreeCreation: "git" | "claudeHooks", with the default unchanged. When enabled for a Claude-backed thread, T3 runs the resolved WorktreeCreate / WorktreeRemove hooks (user, project and local settings, and enabled plugins) instead of git worktree add / remove. It binds the thread to the returned path and falls back to git when no hook is configured. Happy to discuss scope, or to move this to an Ideas discussion if you'd prefer, and to implement it if the direction is approved.
Activity