`EnterWorktree`/`ExitWorktree` Bypassed Almost Entirely — 27:1 Ratio Favoring Raw `git worktree add`, and the One Structured Call's Promised Cleanup Never Fired

Status Open
Reported on v2.1.149
Maintainer reply None cached
Activity 0 comments · opened Jul 22, 2026

Observed on: the underlying events span Claude Code CLI 2.1.149 → 2.1.181 (2026-05-27 through 2026-06-24, per per-session version metadata in my own logs) — the abandoned EnterWorktree call itself was on 2.1.149; the manual cleanup that found it 7 days later was on 2.1.160. This report is being filed against current CLI 2.1.217 (Claude 1.24012.1, 0adcae, 2026-07-21); the tool-usage counts below are cumulative across that whole range, not tied to one version, so they still stand.

Summary

EnterWorktree's own tool output promises a cleanup path: "Use ExitWorktree to leave mid-session, or exit the session to be prompted." I keep a local archive of my own Claude Code session logs — the CLI's own JSONL transcripts, ingested into a queryable database so I can search across every session I've ever run — and cross-referencing it (covering all sessions across ~55 repos) shows two separate problems, not one: (1) that promised cleanup path didn't fire, or wasn't honored, even for the one worktree that went through the structured tool; (2) the structured tool is essentially not how worktrees actually get created in practice — agents overwhelmingly shell out to raw git worktree add instead, so fixing (1) alone would touch only a small fraction of real-world worktree creation.

Evidence — (1): the one structured call, abandoned

Account-wide: EnterWorktree has been called exactly once, ever — 2026-05-27 02:51:42, creating .claude/worktrees/tool-consolidation-spec on branch worktree-tool-consolidation-spec in one of my repos ("Repo A" below). ExitWorktree has been called twice, ever, both 2026-07-07/08, both on unrelated repos, neither matching this worktree. That worktree got no exit call and no honored exit prompt on record. It sat untouched for 7 days until found during unrelated work and force-deleted manually (git branch -D) on 2026-06-03.

Evidence — (2): the tool is bypassed almost entirely

Against that single structured call: 27 separate raw git worktree add invocations via Bash, account-wide, over the same period — the agent shelling out directly for sibling/parallel worktrees rather than using its own dedicated tool. None of these 27 have any structured lifecycle tracking, exit prompt, or hook attached.

Concrete example: in "Repo A" (a mid-size open-source repo I maintain), over 2026-05-25→06-25, 43 of 51 sessions (84%) ran inside a .claude/worktrees/* path. All 43 got there via raw git worktree add, none via EnterWorktree. That repo alone accumulated 25+ stale claude/* branches this way.

Why It Matters

Any fix to EnterWorktree/ExitWorktree's exit-prompt/cleanup logic — including whatever eventually lands for #26725 — only addresses worktrees created through that one tool. Based on the 27:1 ratio, that's a small minority of actual creation. A durable fix needs either (a) making the agent actually prefer EnterWorktree over raw git worktree add, or (b) making cleanup/tracking apply to any worktree under .claude/worktrees/ regardless of creation path.

Related (checked to avoid duplicate-closure)

  • #26725 ("Stale worktrees are never cleaned up," open since 2026-02-18, confirmed still reproducing per a 2026-07-10 comment on CLI v2.1.205) — related but distinct: #26725 is about orphaned worktrees never being GC'd; this is about the exit-prompt not firing even for the intended-path case, and the tool being bypassed at creation time in the overwhelming majority of cases.
  • #78978 / #74708 ("WorktreeRemove hook never fires on removal / at session exit") — related (hook-not-firing is plausibly the same underlying mechanism as the exit-prompt not firing here) but distinct in scope: those are about the removal-hook contract; this is about the creation-path bypass ratio, which neither mentions.
  • #76377 was bot-closed as a duplicate of #26725 before human review — noting only so it isn't cited as independent coverage; its substance is folded into #26725.

View original on GitHub ↗