Native worktree GC (worktree.cleanupPeriodDays) deletes DIRTY and UNPUSHED agent-* worktrees, contradicting the documented clean+pushed-only cleanup behavior

Open 💬 1 comment Opened Jul 6, 2026 by nointerview1548

Environment

  • Claude Code ~v2.1.185-era, macOS (Darwin 25.x, APFS)
  • Project .claude/settings.json: "worktree": { "cleanupPeriodDays": 3 }
  • Subagent (isolation:worktree) worktrees under <project>/.claude/worktrees/agent-*

Repro / observed

Two real subagent worktrees were tracked as controlled subjects; both were removed between
2026-07-02T10:42Z and 2026-07-03T11:12Z once their INTERNAL age (git committer-date / created-time
basis, not filesystem mtime -- touch -t backdating is ignored) crossed the 3-day TTL:

  1. agent-a1792a15fd988a0cd -- DIRTY (uncommitted changes in the tree). REAPED.

The branch stub survived; the uncommitted work is unrecoverable.

  1. agent-a3060652 -- detached HEAD carrying an UNPUSHED commit (no branch ref). REAPED.

No surviving branch/ref anywhere; the commit was recovered only via git fsck --unreachable
and re-anchored as a rescue branch.

Trigger characterization (host-probed 2026-07-02): the sweep fires on a full INTERACTIVE claude
startup (regenerates ~/.claude/.last-cleanup); it does not complete under headless claude -p.

Expected

Per the official worktree docs (code.claude.com/docs/en/worktrees), the cleanup sweep removes only
worktrees that are CLEAN (no uncommitted/untracked changes) AND fully pushed; dirty or unpushed
worktrees are preserved.

Actual

agent-* worktrees are removed once internal age >= cleanupPeriodDays REGARDLESS of
dirty/unpushed state: uncommitted work is destroyed and unpushed commits are orphaned as
unreachable objects.

Workaround (what we deployed)

  • Raised worktree.cleanupPeriodDays 3 -> 30 (native GC demoted to a distant backstop) and wired

our own fail-closed reaper (preserves locked/dirty/unpushed/unaged; never worktree remove --force)
as the sole intentional reaper.

  • Recovery for an already-reaped unpushed commit: git fsck --unreachable -> inspect the commit

objects -> git branch rescue/<name> <sha> + push. Works only until gc/prune expiry, so rescue
promptly.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗