---
name: a-fallthrough-rm-overrides-gits-own-refusal
description: "A teardown script that falls through from `git worktree remove` to `rm -rf` deletes what git just declined to delete — and classifying the POOL never asks the one question a source checkout can answer: is it dirty?"
metadata:
  type: feedback
---

**2026-09-08, mine, sweeping three landed lane worktrees on doyle's word.** My script ran
`git worktree remove --force`, then unconditionally `rm -rf` the same path. All three removes
were REFUSED (`Permission denied`) and the `rm -rf` stripped the trees anyway: 54/40/42 MB →
68K skeletons. Git declined, and my next line overrode it without reading the refusal.

The holder was **rust-analyzer** — identifiable from the artifacts, not guessed: the only
survivors were `<tree>/target/flycheck0/{stderr,stdout}` (a live flycheck) and the editor
emitted fresh diagnostics against a file in one of those trees while the delete ran. Retries
fail as `Device or resource busy` on `crates/spt-daemon`.

**Why:** `git worktree remove` refuses for a REASON (a held handle, a dirty tree, a lock), and
the refusal is the only place that reason is stated. A fall-through turns a stated refusal into
a silent one and does the destructive half regardless. On Windows an IDE indexing a worktree is
the common holder, so this fires most on trees you are not currently working in.

**How to apply:**
- **Gate the `rm` on the git verb succeeding** (`git worktree remove … && rm -rf …`, or better,
  stop and read the refusal). Never sequence destructive steps where a refused first step lets a
  blunter second one proceed.
- **Ask the TREE, not just the pool.** My teardown discipline (outbound reparse, inbound sweep,
  size before/after, `CARGO_TARGET_DIR`) is all about the POOL and answers nothing about the
  checkout. Run `git status --porcelain` in a source worktree BEFORE deleting it; landed lanes
  can still hold uncommitted scratch, and afterwards you cannot tell whether they did.
- **Free space is not a meter for a small delete**: 136 MB removed while free space read 222.89
  → 221.90 GB (it went DOWN, from other box activity). Report the `du` delta and say so.

Related: [[test-profile-pool-outgrows-the-disk-floor]],
[[the-first-claim-on-a-foreign-pool-must-refuse]].

**2026-09-08, later the same day — the rule applied, and what the refusal would have been.**
Reaping a battery worktree, I gated the `rm` on `git worktree remove --force` succeeding and
asked `git status --porcelain` first. Both paid: dirty tracked paths 0 and
`merge-base --is-ancestor origin/main` YES (landed, not believed landed), and the git verb
returned OK so the `rm` never ran — 40 GB by du, nothing to fall through to.

**The holder that would have caused the refusal: EIGHT leaked `spt.exe` whose ExecutablePath was
under `<tree>/target/debug`** — daemon-spawning cells that outlived their runs. A peer's census
said six; there were eight by the time I looked, so re-census yourself rather than acting on a
count someone else took minutes ago. Kill them by VERIFIED ExecutablePath inside the kill loop
(13 other `spt.exe` on the box, including the fleet daemon, must survive), then re-census to
zero before reaping. Sweep for these after ANY battery that runs daemon cells, not just before
a teardown.
