Playbook: Stale Agent-Tool Worktree Cleanup

Created: 2026-07-31 Trigger for this writeup: VSCode/Pylance “large number of source files” warning — root cause was 53 leftover worktree checkouts under .claude/worktrees/, 1.5 GB / ~100,700 files, dating back to 2026-07-06.


Trigger

The Agent(..., isolation: "worktree") option creates its own isolated checkout under .claude/worktrees/agent-<id> — this is separate from, and not covered by, the manual tmp/wt-{issue} convention documented in CLAUDE.md §Agent Workflow — Worktree Branch Isolation. Claude Code auto-removes an isolation: "worktree" checkout only when the agent made no changes; any agent that actually committed work leaves its worktree checkout on disk indefinitely. Over weeks of background-agent usage these accumulate silently — nothing in the CLAUDE.md “clean up immediately after merge” rule applies to this mechanism because it isn’t a tmp/wt-* path a human/orchestrating session created and is tracking.

Symptoms

  • Pylance/VSCode: “found a large number of source files in this workspace”
  • .claude/worktrees/ is large or has many agent-* subdirectories
  • General editor/IDE sluggishness, slow git status in the primary checkout (unrelated worktrees don’t directly slow git status in the main tree, but do slow file-watcher-based tooling)

Confirm

# Size + file count of .claude/worktrees
(Get-ChildItem .claude\worktrees -Recurse -File -Force -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum).Sum /1MB
(Get-ChildItem .claude\worktrees -Recurse -File -Force -ErrorAction SilentlyContinue | Measure-Object).Count
 
# List every registered worktree — real git worktrees show up here even if nested oddly
git worktree list

Before removing anything, verify none are dirty (uncommitted changes) or hold work you still need:

$worktrees = git worktree list --porcelain | Select-String "^worktree (.*\.claude.*)" | ForEach-Object { $_.Matches[0].Groups[1].Value }
foreach ($wt in $worktrees) {
    $dirty = git -C $wt status --porcelain 2>$null
    [PSCustomObject]@{ Path=$wt; Branch=(git -C $wt rev-parse --abbrev-ref HEAD 2>$null); Dirty=[bool]$dirty }
}

Any Dirty=True entry needs manual review (rescue the branch / stash) before removal — see docs/playbooks/shared-checkout-worktree-isolation.md for the “don’t discard, rescue to a branch first” pattern if something looks like real unmerged work.

Fix — remove stale worktree checkouts (branches/commits are NOT affected)

git worktree remove only deletes the working-tree checkout. The branch and every commit on it stay fully intact in git regardless of merge status — safe to remove even for unmerged branches; the branch can be re-checked-out or re-worktreed later if needed.

$worktrees = git worktree list --porcelain | Select-String "^worktree (.*\.claude.*)" | ForEach-Object { $_.Matches[0].Groups[1].Value }
foreach ($wt in $worktrees) { git worktree remove --force $wt }
git worktree prune -v
 
# Remove any leftover empty directories git didn't clean up (non-registered stragglers)
Get-ChildItem .claude\worktrees -Force -ErrorAction SilentlyContinue
Remove-Item .claude\worktrees -Recurse -Force -Confirm:$false -ErrorAction SilentlyContinue

Why the leftover dirs matter (not just disk bloat) — #6006. A leftover unregistered worktree dir is worse than clutter: git commands run from inside it walk up and silently operate on the parent repo’s current branch. Always pair git worktree remove with an explicit Remove-Item/rm -rf of the same path (as above) so no unregistered dir survives, and have every worktree-scoped agent run the pre-write git rev-parse --show-toplevel guard. Full writeup: worktree-remove-fallthrough-guard.md.

.claude/worktrees/ is already in .gitignore, so none of this was ever a git-tracked/committed concern — it’s pure local disk bloat.

Prevention

  • .vscode/settings.json (gitignored, per-workstation) should exclude .claude/worktrees/**, tmp/**, node_modules, .next, .wrangler, .playwright-mcp from python.analysis.exclude, search.exclude, and files.watcherExclude — this stops Pylance/VSCode re-scanning them even if they build back up before the next manual sweep.
  • No automated cleanup exists yet for .claude/worktrees/agent-* specifically (unlike tmp/wt-*, which a human/orchestrating session creates and is expected to git worktree remove right after merge). Until an automated sweep exists, periodically re-run the Confirm + Fix commands above — e.g. as part of an occasional /sr-adjacent housekeeping pass.

Escalation

None needed — this is local workstation disk hygiene, not a production/infra issue. If .claude/worktrees reappears at large scale again shortly after a cleanup, consider filing an issue to add automated pruning (e.g. a nightly local task, or asking upstream Claude Code tooling whether isolation: "worktree" can auto-remove on completion even when the agent made commits).