EnterWorktree validates hook-created (non-git) worktrees against the git worktree list, refusing to enter them

Open 💬 2 comments Opened Jun 9, 2026 by KingMob

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 runs jj 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 EnterWorktree refuses 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/WorktreeRemove hook that produces jj workspaces at a sibling <repo>-workspaces/<name> path (not git-registered worktrees)

Repro steps

  1. In a jj-colocated repo, configure a WorktreeCreate (and WorktreeRemove) hook that creates a non-git worktree — e.g. a jj workspace via jj workspace add — at a sibling path like <repo-parent>/<repo>-workspaces/<name>, and emits that absolute path on stdout per the hook contract.
  2. Start Claude Code normally (no --worktree).
  3. Invoke the EnterWorktree tool mid-session.
  4. Observe: the hook runs and creates the jj workspace, but EnterWorktree then 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 — EnterWorktree previously ignored WorktreeCreate/WorktreeRemove hooks (the hook-firing half; now appears fixed). This report is about the remaining validation half.
  • #60497 — background/agent isolation: "worktree" doesn't fire WorktreeCreate (same class of git-vs-hook inconsistency, different creation path).

View original on GitHub ↗

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