EnterWorktree validates hook-created (non-git) worktrees against the git worktree list, refusing to enter them
Summary
In a jj-colocated repo (git backend + Jujutsu) with a custom WorktreeCreate hook that creates jj workspaces (not git worktrees), the EnterWorktree tool is internally inconsistent:
- The creation path correctly honors the custom hook — it fires
WorktreeCreate, the hook runsjj workspace add, and a jj workspace is created at a sibling path. - The enter/validation path then assumes git worktrees and validates the just-created path against the git worktree list. Because a jj workspace is not a registered git worktree, validation fails and
EnterWorktreerefuses to switch the session into the directory the hook just created.
So the same tool delegates creation to a non-git hook, then immediately rejects the hook's output for not being a git worktree. The two halves disagree about what a "worktree" is.
(Note: this is distinct from #36205 — there the hook was not fired at all and a built-in worktree-add ran. As of v2.1.169 the hook now fires correctly; the remaining defect is that the result is validated against the git worktree list.)
Environment
- Claude Code: 2.1.169
- OS: macOS 26.4.1 (build 25E253), Darwin 25.4.0, arm64
- VCS: Jujutsu (jj) colocated git repo, with a custom
WorktreeCreate/WorktreeRemovehook that produces jj workspaces at a sibling<repo>-workspaces/<name>path (not git-registered worktrees)
Repro steps
- In a jj-colocated repo, configure a
WorktreeCreate(andWorktreeRemove) hook that creates a non-git worktree — e.g. a jj workspace viajj workspace add— at a sibling path like<repo-parent>/<repo>-workspaces/<name>, and emits that absolute path on stdout per the hook contract. - Start Claude Code normally (no
--worktree). - Invoke the
EnterWorktreetool mid-session. - Observe: the hook runs and creates the jj workspace, but
EnterWorktreethen rejects it.
Actual behavior
The hook successfully creates the jj workspace, then EnterWorktree validates it against the git worktree list and refuses to enter:
Cannot enter worktree: /Users/<user>/Code/.../swarm-infra-workspaces/import-gap-audit-report is not a registered worktree of /Users/<user>/Code/.../swarm-infra. Run 'git -C /Users/<user>/Code/.../swarm-infra worktree list' to see registered worktrees.
The workspace genuinely exists — it just isn't a git worktree. jj workspace list from the repo root confirms it (paths abbreviated):
$ jj workspace list
default: ppspyqzx 487583bb (empty) (no description set)
import-gap-audit-report: mypqqpnz eb08415b (empty) (no description set)
maybe-add-skaffold: mqlstzsp 5a09da1a (empty) (no description set)
import-gap-audit-report is a real, hook-created jj workspace that the git worktree list will never show — yet that git list is what the validation gates on.
Expected behavior
When worktree creation is delegated to a custom hook (WorktreeCreate), EnterWorktree must not validate the result against the git worktree list. It should either validate against the hook's notion of worktrees, or trust the hook's emitted stdout path and skip the git check entirely, then switch the session cwd into the hook-created directory.
Put simply: if creation is hook-driven (non-git), validation must be hook-driven (non-git) too. The create path and the enter/validate path should use a consistent definition of "worktree."
Impact
In a background session this is a hard block. The background write-isolation guard rejects all Write/Edit tool calls until EnterWorktree "succeeds" — and because validation always fails for a jj workspace, it never succeeds. The only workaround is to bypass EnterWorktree entirely and write directly to the hook-created workspace path (e.g. via Bash heredocs), defeating the isolation tooling.
Config (redacted)
The relevant hook block from ~/.claude/settings.json (secrets/tokens redacted; none appear in these hooks):
"worktree": { "baseRef": "fresh" },
"hooks": {
"WorktreeCreate": [
{
"hooks": [{
"type": "command",
"command": "... create-worktree.sh \"$cwd\" \"$name\" (reads name+cwd from hook JSON on stdin)",
"timeout": 180
}]
}
],
"WorktreeRemove": [
{
"hooks": [{ "type": "command", "command": "... remove-worktree.sh \"$repo\" \"$name\"" }]
}
]
}
The create-worktree.sh hook handles both VCS backends and, for a jj repo, runs jj workspace add, creating the workspace at <repo-parent>/<repo>-workspaces/<name> and printing that absolute path on stdout (the documented WorktreeCreate stdout contract).
Related
- #36205 —
EnterWorktreepreviously ignoredWorktreeCreate/WorktreeRemovehooks (the hook-firing half; now appears fixed). This report is about the remaining validation half. - #60497 — background/agent
isolation: "worktree"doesn't fireWorktreeCreate(same class of git-vs-hook inconsistency, different creation path).
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗