Automatic worktree cleanup deletes directories actively in use by running sessions
Summary
Something inside Claude Code — not a user script, cron job, launchd agent, or anything in .claude/hooks — periodically deletes git worktree directories under .claude/worktrees/, including ones that a currently running session has as its cwd and is actively issuing tool calls against. This happened three times to three different concurrent sessions within a ~10-minute window, one of them deleted mid-Bash-command out from under the tool call itself.
Environment
- macOS Darwin 24.6.0 (Sonoma/Sequoia-class), Claude Code CLI
- Repo: a large git repo (~80 historical worktree entries recorded for it in
~/.claude.json'sprojectsmap) with many concurrent Claude Code sessions running in separate.claude/worktrees/<name>checkouts at once - Multiple sessions, multiple repos, running simultaneously via the local Claude Code app
What happened
On 2026-08-20, between roughly 13:28 and 13:37 UTC, three separate, simultaneously-running sessions each had their worktree directory physically removed while in use:
- Session A (read-only research task) — working directory
.claude/worktrees/wdpa-aug2026-usage-455e01, branchclaude/wdpa-aug2026-usage-455e01. The directory disappeared mid-task;git worktree listno longer showed it afterward. The branch itself was not deleted (still resolvable viagit show <branch>:<path>), but the checkout was gone. - Session B (my own investigating session) — working directory
.claude/worktrees/jolly-margulis-398686, branchclaude/jolly-margulis-398686, whose tip was identical toorigin/main's tip (zero unique commits) with a fully clean working tree. Mid-Bash-command, the tool result came back as:
```
Working directory "/Users/.../canopy/.claude/worktrees/jolly-margulis-398686" was deleted; shell cwd recovered to "/Users/lisaswerling". Re-issue your command (it will run from the recovered directory).
git worktree list` immediately afterward no longer listed it at all.
- Session C (a different, unrelated task) — had since been provisioned into the same path as session A (
.claude/worktrees/wdpa-aug2026-usage-455e01) under yet another freshly-generated branch name (claude/recursing-mendel-faf4de). It hit the identical "was deleted; shell cwd recovered" error, at essentially the same time as session B.
All three deleted worktrees had one thing in common: their branch tip was a confirmed ancestor of origin/main with a clean working tree — i.e., "no unique commits, nothing uncommitted." That is a legitimate signal for "this worktree's content is fully captured elsewhere," but it is not a legitimate signal for "no one is using this directory right now." A read-only research session, or any session that simply hasn't committed yet, produces exactly that git-state fingerprint while very much in active use.
Impact / why this is serious
- A worktree can look "safe to delete" by git state while a live session has files open, is mid-read, or is about to write to it. In this incident nothing was lost only because none of the three affected sessions had uncommitted changes yet — but the fingerprint used ("ancestor of main + clean tree") says nothing about liveness, so a session that pauses momentarily between edits (e.g., mid-multi-file refactor, working tree transiently clean between a
git commitand the next edit) would be an equally valid deletion target under whatever logic did this, and next time the loss would be real, uncommitted work. - It happened to multiple unrelated concurrent sessions in the same short window, meaning whatever is doing this sweeps broadly across all worktrees for a repo (or across repos), not narrowly at a single session's own state.
- Failure is silent from the deleting side — the affected session only finds out because its next tool call errors out. There's no warning, no grace period, no check for an in-flight tool call.
What I ruled out as the trigger
crontab -l: no relevant entry.~/Library/LaunchAgents/: no relevant.plist..claude/hooks/: only one hook present (block-broad-scan.sh), unrelated (aPreToolUseguard against unscopedgrep -r/find /), not a cleanup routine.- Disk pressure: not a factor (>800GB free on all relevant volumes at the time).
- macOS unified log (
log show --predicate 'eventMessage CONTAINS "worktree"') for the affected window: no matching entries — whatever did this isn't logging throughos_log/NSLog.
Leads pointing at an internal, unattended feature
~/.claude/.last-cleanupis a plain timestamp file that the app itself rewrites periodically and unprompted. I observed it advance from2026-08-20T13:28:52.325Zto2026-08-20T13:44:04.933Z— roughly a 15-minute cadence — with no corresponding user action. This looks like a periodic background routine's completion marker.~/.claude.json(top-level CLI state) contains agroveConfigCachekey:{"<id>": {"grove_enabled": true, "timestamp": ...}}. "Grove" reads as a plausible internal codename for worktree-lifecycle management (it fits thematically withgit worktree, and this harness's own tool surface already exposesEnterWorktree/ExitWorktreeprimitives). This flag isn't present in the locally-cached GrowthBook/experiment feature maps (cachedGrowthBookFeatures,cachedExperimentFeatures,cachedExperimentData) and isn't exposed in~/.claude/settings.json, suggesting it's a remotely-controlled rollout flag rather than a local, user-toggleable setting.- The
projectsmap in~/.claude.jsonstill carries stale entries for both deleted worktree paths (just generic per-directory settings likehasTrustDialogAccepted, no lifecycle metadata, no "last active" timestamp, no cleanup threshold) — so whatever decision logic exists isn't obviously reading from that registry either. It may be querying live git state directly (git worktree list+ ancestor/clean checks) each sweep.
What I could not determine
- The exact code path or binary responsible (Claude Code's app internals aren't introspectable from a shell).
- Whether
grove_enabledis in fact the feature responsible, or merely correlated. - Whether there's any existing opt-out.
Suggested fix
Before removing a worktree automatically, check for a live session currently using that path (e.g., an open process with that directory as its cwd, or an in-flight tool call) in addition to the git-state ancestor/clean check. Git state alone ("ancestor of origin/main" + "clean tree") is necessary but not sufficient to conclude a worktree is abandoned — it's indistinguishable from a live read-only session or a live session between commits.
Repro
Not reliably reproducible on demand (I couldn't identify the trigger condition), but reliably observed: run several concurrent Claude Code sessions in separate worktrees off the same repo, where at least one worktree's branch tip coincides with origin/main and its tree is clean (e.g., a freshly created worktree with no commits yet, or a worktree used only for read-only research via git show), and leave it open for ~15+ minutes.