Background sessions from a non-git dir deadlock when any WorktreeCreate hook is configured (hooks can't decline)

Open 💬 0 comments Opened Jul 2, 2026 by pandysp

Summary

I ship a WorktreeCreate hook in a plugin (it routes Claude's worktree creation through worktrunk). With the hook enabled at user scope, every background session launched from a non-git folder is dead on arrival. The write guard blocks Write/Edit until EnterWorktree runs. EnterWorktree always calls the hook. The hook can't create a worktree without a repo so it fails. The session can neither write nor isolate.

The gap underneath: a hook has no way to say "nothing to isolate here, act as if no hook were configured". Exit 0 with a path and non-zero exit are the only two outcomes and for a non-git cwd both are wrong.

Reproduction

// user settings.json. `false` stands in for any VCS-specific hook
// that can't work outside a repo (mine runs worktrunk).
"hooks": { "WorktreeCreate": [{ "hooks": [{ "type": "command", "command": "false" }] }] }
mkdir /tmp/not-a-repo && cd /tmp/not-a-repo
CLAUDE_CODE_SESSION_KIND=bg claude -p "use the Write tool to create ./x.txt"

Write is blocked with "This background session hasn't isolated its changes yet. Call EnterWorktree first...". EnterWorktree fails with "WorktreeCreate hook failed". Deadlock.

Remove the hook and the same command just writes the file. The guard knows there is nothing to isolate in a non-git dir. Configuring a hook anywhere flips that off. The docs say as much: isolation is skipped when "the working directory isn't a git repository AND no WorktreeCreate hook is configured".

(CLAUDE_CODE_SESSION_KIND=bg is what the daemon sets on background jobs. Setting it by hand makes the repro scriptable. The real thing happens with any background session dispatched from a non-git folder like ~.)

Workaround

My hook now detects the non-repo case and echoes the cwd back with exit 0. The session "enters" a worktree that is its own working dir and writes proceed in place. That is the behavior I want but I get it by lying:

  • The session reports "Created worktree at /Users/me". No worktree was created and nothing is isolated. The agent inside believes its changes are isolated when they aren't.
  • The hooks doc says the returned path must differ from cwd and be a registered git worktree. Neither is checked on 2.1.198. If a future version enforces the doc, the deadlock comes back.

Suggested fix

Give WorktreeCreate hooks a decline signal. A designated exit code or a decision field in the JSON output like PreToolUse already has. Semantics: proceed exactly as if no WorktreeCreate hook were configured for this invocation. I don't mind the exact mechanism.

Prior issues

  • #60418 got the guard condition documented (closed, docs only). It confirms that configuring a hook opts non-git dirs into isolation.
  • #66503 is the same deadlock through a different door: a jj-workspace hook creates the worktree and EnterWorktree rejects it as not a git worktree. Their impact section describes exactly this hard block in background sessions.

Environment

  • Claude Code 2.1.198
  • macOS (Darwin 25.5.0, arm64)
  • Hook: worktrunk-hook (pandysp/claude-plugins), worktrunk 0.52+

View original on GitHub ↗