LearnNewsExamplesServices
Frontmatter
id12677
titleSimplify worktree prune: keep current, delete the rest, hydrate
stateClosed
labels
enhancementairefactoring
assigneesneo-gpt
createdAtJun 7, 2026, 7:58 AM
updatedAtJun 7, 2026, 6:25 PM
githubUrlhttps://github.com/neomjs/neo/issues/12677
authorneo-opus-grace
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtJun 7, 2026, 6:25 PM

Simplify worktree prune: keep current, delete the rest, hydrate

Closed v13.0.0/archive-v13-0-0-chunk-16 enhancementairefactoring
neo-opus-grace
neo-opus-grace commented on Jun 7, 2026, 7:58 AM

Context

ada's git-worktree sprawl (>20–50, manually deleted by @tobiu on 2026-06-07) is a standing system-load source: each worktree under .claude/worktrees/ is a full checkout that WebStorm re-indexes, fsnotifier watches, and the KB-sync can re-embed. Tonight that compounded into WebStorm runaways (1100–1314% CPU, 160–246 threads) + memory pressure (~3.4G free) + MC embedder saturation. The cleanup that should prevent the sprawl doesn't fire and doesn't delete.

The existing prune — ai:prune-worktreesnode ai/scripts/migrations/bootstrapWorktree.mjs --prune-stale (pruneStaleWorktrees()) — fails for three reasons:

  1. The npm script (package.json:47) runs --prune-stale without --apply → dry-run; it reports prunable worktrees but deletes nothing.
  2. No auto-trigger exists in the tracked repo (no launchd/cron/orchestrator/git-hook; no >50 threshold caller — that's harness-private/unwired).
  3. The criteria preserve too much: prunable = branch merged/patch-equivalent to base, or branch gone (bootstrapWorktree.mjs ~654–744). Dirty/unmerged/active worktrees are kept indefinitely.

@tobiu's framing: the merge-classification / PR-status / dirty taxonomy is over-careful — "typical opus-4.6 model code" — and is itself the root cause. By trying never to delete something potentially-valuable, it preserves exactly the abandoned worktrees that accumulate into sprawl.

The Problem

A git worktree is a disposable checkout: committed work lives in branches/remotes, not in the worktree. So "preserve unmerged/dirty worktrees" mostly preserves abandoned cruft — the sprawl. The safety machinery creates the problem it's trying to avoid. The correct model is simple and aggressive: keep only the current/active worktree; delete the rest; hydrate the current.

The Architectural Reality

  • ai/scripts/migrations/bootstrapWorktree.mjs:
    • pruneStaleWorktrees() (~766) — prune entry; default never-mutates (dry-run; apply=false, ~751–757).
    • Merge-classification to remove: isMergedToBase(), PR-status gating, the dirty/unmerged/prunable-merged taxonomy + removeArgs (~654–744).
    • Hydrate side to reuse: --link-data / materializeServerConfigTemplate / symlinked subdirs (~137/335/483).
    • Current-worktree resolution via git worktree list --porcelain (~575).
  • package.json:47ai:prune-worktrees (currently dry-run).
  • Worktrees live under .claude/worktrees/<name>/ (per #11654).

The Fix

Replace the careful classification with a flat keep-current / delete-rest + hydrate:

  1. git worktree list --porcelain → identify the current/active worktree.
  2. For every other worktree path → git worktree remove --force <path> (force = no merge/dirty gate).
  3. Run the existing hydrate on the current worktree (--link-data + symlink/config-template setup).
  4. Make the prune delete by default (--apply semantics); demote dry-run to an explicit --dry-run opt-in; update ai:prune-worktrees.
  5. Add a scheduled trigger (launchd/cron, or a documented periodic invocation) — not a count-threshold hook.
  6. Delete isMergedToBase, the PR-status checks, and the dirty/unmerged/age taxonomy — net code reduction.

Contract Ledger

Target Surface Source of Authority Proposed Behavior Fallback Evidence
ai:prune-worktrees (npm) package.json:47 dry-run → deletes all-but-current + hydrates --dry-run opt-in this ticket
--prune-stale flag bootstrapWorktree.mjs criteria merged-only → all-but-current current-worktree guard ~654-766
scheduled prune trigger new (launchd/cron) none → interval-based run manual npm run this ticket

Decision Record impact

none (no ADR governs worktree pruning). Supersedes the merge-based approach from #11654 (closed).

Acceptance Criteria

  • --prune-stale removes ALL worktrees except the current/active one (git worktree remove --force), then hydrates the current via the existing --link-data/symlink setup.
  • The current/active worktree is never removed — explicit guard + a unit test asserting it.
  • Merge-classification / PR-status / dirty taxonomy / any age-TTL logic is removed (net deletion; the diff is smaller than the code it replaces).
  • Prune deletes by default (--apply semantics); dry-run is an explicit --dry-run; ai:prune-worktrees updated + documented.
  • A scheduled trigger runs the prune on an interval (not a count-threshold hook).
  • Unit test: keep-current/delete-rest classification over a mocked git worktree list --porcelain.

Out of Scope

  • The harness-private ">50 hook" (ada's side) — this ticket fixes the tracked script + a scheduled trigger; the harness hook can simply call the corrected script.
  • The broader OUR-team-vs-fork .agents/workflows separation (#11837 successor).
  • The miniSummary backfill (#12673) / context-recovery skill (#12674) — sibling recency work, unrelated.

Avoided Traps / Gold Standards Rejected

  • Do NOT re-introduce merge/age/PR "safety" classification. That over-caution is what produced the sprawl; a worktree is disposable (committed work is in branches/remotes). Simpler-aggressive is the correct default here, not a risk.
  • Do NOT gate on a count threshold (fragile; fires only on a bound event). A scheduled interval is robust.

Related

  • Supersedes the merge-based prune from #11654 (closed) + #10432 / PR #10433.
  • Systemic root of the recurring load this session (worktree sprawl → WebStorm reindex / fsnotifier / KB re-embed).
  • Coordinate with @neo-opus-ada (author of bootstrapWorktree.mjs).

Origin Session ID: c28bcd14-850c-45e3-aae7-3655cdb7127c

Handoff Retrieval Hints: query_raw_memories "worktree prune delete-all-except-current hydrate over-careful"; files ai/scripts/migrations/bootstrapWorktree.mjs (pruneStaleWorktrees), package.json (ai:prune-worktrees).

Authored by Claude (Opus 4.8, neo-opus-grace). Diagnosed this session; design corrected by @tobiu (drop the over-careful merge-classification).

tobiu closed this issue on Jun 7, 2026, 6:25 PM
tobiu referenced in commit bc3989d - "feat(ai): simplify worktree pruning (#12677) (#12678)" on Jun 7, 2026, 6:25 PM