Summary
Worktrees accumulate without bound across this fleet and nothing reaps them. A
hygiene pass today found 142 linked worktrees across 6 different roots,
of which 115 were clean and disposable — no tracked changes, no untracked
files, not locked. They had simply never been removed. Only 27 held real
uncommitted state.
Nothing in the current tooling classifies a clean-but-stale worktree as
reclaimable, so they are invisible to every audit and survive indefinitely.
The measured state before cleanup
| Root |
Registrations |
<ghq>/melodic-software/claude-code-plugins/.claude/worktrees |
55 |
<ghq>/melodic-software (siblings) |
58 (incl. 11 canonical) |
~/.claude/plugins/data/source-control-melodic-software/worktrees |
37 |
~/.claude-loop-worktrees |
38 |
C:/tmp |
6 |
~/.codex/worktrees/* |
2 |
| system temp / dead-session scratchpad |
2 |
After removing every clean one: 11 canonical + 27 linked remain, all 27 holding
uncommitted work or locked.
Defect 1: no disposability lane
repo-fleet-hygiene:audit classifies worktrees as merged-worktree,
prunable-worktree, missing-worktree, worktree-admin-mismatch, or
locked-worktree. There is no category for clean worktree, branch not
merged — which was the state of most of the 115.
That gap matters because worktree disposability is independent of merge state.
Removing a clean worktree destroys nothing: the branch ref and every commit live
in the canonical repository. The only thing a worktree uniquely holds is
uncommitted work. So the correct predicate is:
disposable <=> no tracked changes AND no untracked files AND not locked
Merge status is irrelevant. During this pass I initially gated removal on "the
branch is also deletable" and consequently removed only 55 of 115 — the same
conservatism the audit's taxonomy encodes.
Requested: a reclaimable-worktree finding for any clean, unlocked linked
worktree, with age or last-commit recency as the ranking signal rather than merge
state.
Defect 2: git stash list in a linked worktree reports the whole repository
A trap worth encoding in whichever helper computes worktree state. refs/stash
is shared per repository, so git stash list run inside a linked worktree
returns the canonical repo's entire stash list, not that worktree's. It reads
non-zero for every worktree of any repo that has ever stashed.
I gated removal on stash == 0 and it silently excluded 113 of 115 candidates.
Stashes survive worktree removal, so stash count must not be part of a
disposability predicate. Any shared worktree helper should either not expose it
per-worktree or name it repo_stash_count.
Defect 3: six roots, one of which the documented convention forbids
The documented policy (user-scope doc ghq-layout-sibling-pr-worktrees.md) is a
sibling layout, <canonical>-pr-<number>, and it explicitly rules out
.claude/worktrees/ inside the repo when the canonical checkout is a ghq tree.
Yet 55 registrations were in exactly that forbidden location, nested inside
claude-code-plugins' own working tree, and three more roots exist that the doc
does not mention at all.
Naming is equally scattered. Observed schemes among siblings alone:
standards-pr-223 (the documented form — 1 of 23)
ci-workflows-pr281 (no separator before the number)
claude-code-plugins-issue-1017 (issue, not PR)
claude-code-plugins-1211 (bare number)
claude-code-plugins-1285-argparse (number plus slug)
medley-plugin-user-scope (no number at all)
and elsewhere worktree-agent-<17-hex>, ccp-1269, 609, 848-sigfix,
drive-1503-v2, wip/lane-snapshot/<slug>.
Requested: one resolver that every worktree-creating skill calls, so location and
naming cannot drift per caller, plus a check that fails when a registration
appears outside the sanctioned roots.
Defect 4: removal leaves shells behind when workspace symlinks exist
git worktree remove succeeded but left a directory tree behind for a medley
worktree, because npm workspace symlinks under
.claude/skills/*/extraction/node_modules/@melodic/ were not created by git and
are not removed by it. The links dangled (their targets were inside the removed
tree), so find -type f reported zero files while rmdir refused with
"Directory not empty" — the residue is symlinks, which neither predicate sees.
Worth handling in whichever skill wraps worktree removal: after a successful
git worktree remove, if the path still exists, report it, and check for
non-directory non-file entries rather than assuming "no files" means empty.
Impact
Beyond disk, the accumulation actively blocks hygiene: an attached branch cannot
be deleted, so 115 stale worktrees were pinning branches that were otherwise
provably merged. Cleanup had to run worktree-removal before branch-deletion for
55 branches in this pass.
Environment
- 11 canonical checkouts,
melodic-software org, ghq layout on D:/repos
- Git 2.54.0.windows.1, Claude Code on Windows 11 Pro 10.0.26200
- Observed 2026-07-29
Summary
Worktrees accumulate without bound across this fleet and nothing reaps them. A
hygiene pass today found 142 linked worktrees across 6 different roots,
of which 115 were clean and disposable — no tracked changes, no untracked
files, not locked. They had simply never been removed. Only 27 held real
uncommitted state.
Nothing in the current tooling classifies a clean-but-stale worktree as
reclaimable, so they are invisible to every audit and survive indefinitely.
The measured state before cleanup
<ghq>/melodic-software/claude-code-plugins/.claude/worktrees<ghq>/melodic-software(siblings)~/.claude/plugins/data/source-control-melodic-software/worktrees~/.claude-loop-worktreesC:/tmp~/.codex/worktrees/*After removing every clean one: 11 canonical + 27 linked remain, all 27 holding
uncommitted work or locked.
Defect 1: no disposability lane
repo-fleet-hygiene:auditclassifies worktrees asmerged-worktree,prunable-worktree,missing-worktree,worktree-admin-mismatch, orlocked-worktree. There is no category for clean worktree, branch notmerged — which was the state of most of the 115.
That gap matters because worktree disposability is independent of merge state.
Removing a clean worktree destroys nothing: the branch ref and every commit live
in the canonical repository. The only thing a worktree uniquely holds is
uncommitted work. So the correct predicate is:
Merge status is irrelevant. During this pass I initially gated removal on "the
branch is also deletable" and consequently removed only 55 of 115 — the same
conservatism the audit's taxonomy encodes.
Requested: a
reclaimable-worktreefinding for any clean, unlocked linkedworktree, with age or last-commit recency as the ranking signal rather than merge
state.
Defect 2:
git stash listin a linked worktree reports the whole repositoryA trap worth encoding in whichever helper computes worktree state.
refs/stashis shared per repository, so
git stash listrun inside a linked worktreereturns the canonical repo's entire stash list, not that worktree's. It reads
non-zero for every worktree of any repo that has ever stashed.
I gated removal on
stash == 0and it silently excluded 113 of 115 candidates.Stashes survive worktree removal, so stash count must not be part of a
disposability predicate. Any shared worktree helper should either not expose it
per-worktree or name it
repo_stash_count.Defect 3: six roots, one of which the documented convention forbids
The documented policy (user-scope doc
ghq-layout-sibling-pr-worktrees.md) is asibling layout,
<canonical>-pr-<number>, and it explicitly rules out.claude/worktrees/inside the repo when the canonical checkout is a ghq tree.Yet 55 registrations were in exactly that forbidden location, nested inside
claude-code-plugins' own working tree, and three more roots exist that the docdoes not mention at all.
Naming is equally scattered. Observed schemes among siblings alone:
standards-pr-223(the documented form — 1 of 23)ci-workflows-pr281(no separator before the number)claude-code-plugins-issue-1017(issue, not PR)claude-code-plugins-1211(bare number)claude-code-plugins-1285-argparse(number plus slug)medley-plugin-user-scope(no number at all)and elsewhere
worktree-agent-<17-hex>,ccp-1269,609,848-sigfix,drive-1503-v2,wip/lane-snapshot/<slug>.Requested: one resolver that every worktree-creating skill calls, so location and
naming cannot drift per caller, plus a check that fails when a registration
appears outside the sanctioned roots.
Defect 4: removal leaves shells behind when workspace symlinks exist
git worktree removesucceeded but left a directory tree behind for amedleyworktree, because npm workspace symlinks under
.claude/skills/*/extraction/node_modules/@melodic/were not created by git andare not removed by it. The links dangled (their targets were inside the removed
tree), so
find -type freported zero files whilermdirrefused with"Directory not empty" — the residue is symlinks, which neither predicate sees.
Worth handling in whichever skill wraps worktree removal: after a successful
git worktree remove, if the path still exists, report it, and check fornon-directory non-file entries rather than assuming "no files" means empty.
Impact
Beyond disk, the accumulation actively blocks hygiene: an attached branch cannot
be deleted, so 115 stale worktrees were pinning branches that were otherwise
provably merged. Cleanup had to run worktree-removal before branch-deletion for
55 branches in this pass.
Environment
melodic-softwareorg, ghq layout onD:/repos