Automatic worktree cleanup deletes directories actively in use by running sessions

Status Open
Maintainer reply None cached
Activity 0 comments · opened Aug 20, 2026

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's projects map) 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:

  1. Session A (read-only research task) — working directory .claude/worktrees/wdpa-aug2026-usage-455e01, branch claude/wdpa-aug2026-usage-455e01. The directory disappeared mid-task; git worktree list no longer showed it afterward. The branch itself was not deleted (still resolvable via git show <branch>:<path>), but the checkout was gone.
  2. Session B (my own investigating session) — working directory .claude/worktrees/jolly-margulis-398686, branch claude/jolly-margulis-398686, whose tip was identical to origin/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.

  1. 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 commit and 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 (a PreToolUse guard against unscoped grep -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 through os_log/NSLog.

Leads pointing at an internal, unattended feature

  • ~/.claude/.last-cleanup is a plain timestamp file that the app itself rewrites periodically and unprompted. I observed it advance from 2026-08-20T13:28:52.325Z to 2026-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 a groveConfigCache key: {"<id>": {"grove_enabled": true, "timestamp": ...}}. "Grove" reads as a plausible internal codename for worktree-lifecycle management (it fits thematically with git worktree, and this harness's own tool surface already exposes EnterWorktree/ExitWorktree primitives). 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 projects map in ~/.claude.json still carries stale entries for both deleted worktree paths (just generic per-directory settings like hasTrustDialogAccepted, 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_enabled is 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.

View original on GitHub ↗