[WorktreePool] reaps a worktree while its leasing session is actively running (3s after the session's last command)

Status Open
Reported on v2.1.212
Maintainer reply None cached
Activity 2 comments · opened Jul 17, 2026

[WorktreePool] removed a worktree while the session leasing it was actively running — 3 seconds after that session's last successful command. The removal log line names the live lease and removes it anyway.

Nothing was lost in my case: the session had committed and pushed seconds earlier. The same event with uncommitted work in the tree would be silent data loss, and I can't find anything that would have prevented it.

Environment

  • Claude Code 2.1.212, desktop app 1.20186.9 (CCD 2.1.209)
  • macOS 26.5.1 (Apple Silicon)
  • Worktrees under the hardcoded <repo>/.claude/worktrees/
  • High concurrency: ~10 local sessions against one repo

What the log shows

From ~/Library/Logs/Claude/main.log (timestamps local, UTC-5):

20:00:48 [info] [WorktreePool] Reaping idle worktree <wt-a> in /Users/<user>/projects/<repo>
20:00:48 [info] Removing worktree "<wt-a>" (leased by local_c0eba6de-…): /Users/<user>/projects/<repo>/.claude/worktrees/<wt-a>
20:00:48 [info] Removed worktree "<wt-a>"
20:00:48 [info] [WorktreePool] Reaping idle worktree <wt-b> in /Users/<user>/projects/<repo>
20:00:48 [info] Removing worktree "<wt-b>" (leased by local_ceadb07b-…): …
20:00:49 [info] Removed worktree "<wt-b>"

Both worktrees were leased by sessions that were alive. Two live sessions died in one pass.

The session was demonstrably not idle

The victim session's own transcript brackets the reap (times UTC; 20:00:48 local = 01:00:48Z):

| Time (UTC) | Event |
|---|---|
| 01:00:37 | Bash: git push -u origin <branch> + gh pr create |
| 01:00:41 | Succeeds — PR created |
| 01:00:45 | Bash: gh pr view <n> --json autoMergeRequest |
| 01:00:48 | [WorktreePool] Reaping idle worktreeRemoved |
| 01:01:03 | failed to run git: fatal: Unable to read current working directory: No such file or directory |
| 01:01:11 | Working directory "…/.claude/worktrees/<wt-a>" was deleted; shell cwd recovered to "/Users/<user>" |

A Bash tool call was in flight when the worktree was removed. Whatever idle means to the pool here, it does not mean "no active work" — and it isn't the lease, since the lease is printed on the very line that removes it.

The worktree had been created by the app ~36 minutes earlier for this same session, which ran near-continuously throughout (including a 241-second test run — possibly relevant if idle is measured by something that a long-running tool call doesn't refresh).

Frequency

In a single day's main.log:

  • 29 Reaping idle worktree events.
  • 29/29 attempted removals logged Removed — no failed to remove, no contains modified files, no refusal of any kind.
  • Not on a fixed schedule: reaps at 06:08 07:38 12:07 17:53 18:23 19:30 20:00. Neighbouring [WarmLifecycle] lines log Starting idle timeout for local_<session>: 900s and 1800s, suggesting a per-session idle timer in the 15–30 min range.

Questions

  1. Should [WorktreePool] reap a worktree whose lease is held by a live session? From the outside this reads like idle-detection consulting a timer that an active session doesn't reset, while the lease check is logged but not enforced.
  2. Does the removal use git worktree remove --force? This is the part I couldn't determine and it decides the blast radius. 29/29 removals succeeding with zero refusals is consistent either with --force (which discards uncommitted edits) or with no dirty tree ever having been reaped. If it force-removes, any uncommitted work in a reaped worktree is gone with no warning and no recovery.

Impact

<repo>/.claude/worktrees is hardcoded (function lAt(e){return tu.join(e,".claude","worktrees")}) with no setting to relocate it, so users can't move worktrees somewhere the pool won't reach. On this machine the only workable mitigation has been "commit and push immediately, always" — an active session is not protected by being active.

Happy to supply fuller log excerpts if useful.

View original on GitHub ↗

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