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-worktrees → node ai/scripts/migrations/bootstrapWorktree.mjs --prune-stale (pruneStaleWorktrees()) — fails for three reasons:
- The npm script (
package.json:47) runs --prune-stale without --apply → dry-run; it reports prunable worktrees but deletes nothing.
- No auto-trigger exists in the tracked repo (no launchd/cron/orchestrator/git-hook; no
>50 threshold caller — that's harness-private/unwired).
- 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:47 — ai: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:
git worktree list --porcelain → identify the current/active worktree.
- For every other worktree path →
git worktree remove --force <path> (force = no merge/dirty gate).
- Run the existing hydrate on the current worktree (
--link-data + symlink/config-template setup).
- Make the prune delete by default (
--apply semantics); demote dry-run to an explicit --dry-run opt-in; update ai:prune-worktrees.
- Add a scheduled trigger (launchd/cron, or a documented periodic invocation) — not a count-threshold hook.
- 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
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).
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,fsnotifierwatches, 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-worktrees→node ai/scripts/migrations/bootstrapWorktree.mjs --prune-stale(pruneStaleWorktrees()) — fails for three reasons:package.json:47) runs--prune-stalewithout--apply→ dry-run; it reports prunable worktrees but deletes nothing.>50threshold caller — that's harness-private/unwired).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).isMergedToBase(), PR-status gating, the dirty/unmerged/prunable-mergedtaxonomy +removeArgs(~654–744).--link-data/materializeServerConfigTemplate/ symlinked subdirs (~137/335/483).git worktree list --porcelain(~575).package.json:47—ai:prune-worktrees(currently dry-run)..claude/worktrees/<name>/(per #11654).The Fix
Replace the careful classification with a flat keep-current / delete-rest + hydrate:
git worktree list --porcelain→ identify the current/active worktree.git worktree remove --force <path>(force = no merge/dirty gate).--link-data+ symlink/config-template setup).--applysemantics); demote dry-run to an explicit--dry-runopt-in; updateai:prune-worktrees.isMergedToBase, the PR-status checks, and the dirty/unmerged/age taxonomy — net code reduction.Contract Ledger
ai:prune-worktrees(npm)package.json:47--dry-runopt-in--prune-staleflagbootstrapWorktree.mjsnpm runDecision Record impact
none(no ADR governs worktree pruning). Supersedes the merge-based approach from #11654 (closed).Acceptance Criteria
--prune-staleremoves ALL worktrees except the current/active one (git worktree remove --force), then hydrates the current via the existing--link-data/symlink setup.--applysemantics); dry-run is an explicit--dry-run;ai:prune-worktreesupdated + documented.git worktree list --porcelain.Out of Scope
.agents/workflowsseparation (#11837 successor).Avoided Traps / Gold Standards Rejected
Related
fsnotifier/ KB re-embed).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"; filesai/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).