---
name: gate-target-disposal-is-a-rig-step
description: "Throwaway-target deletion is the LAST LEG of every gate battery on a CI-runner box — a rig step, not a remembered courtesy"
metadata: 
  node_type: memory
  type: feedback
  originSessionId: f290ac0f-f897-4dce-95ca-1e71f27ae4a8
  modified: 2026-08-05T01:08:38.187Z
---

Fleet ruling (doyle, binding, 2026-07-29). A 47GB gate throwaway target left on HFENDULEAM dropped C: to 10GB and the n1-gate free-space floor correctly killed PR #123's Windows CI. Census then found ~28GB more of dead gate targets in session scratchpads — including doyle's own.

The three legs, now rig STEPS:

1. **Throwaway-target deletion is the LAST LEG of every gate battery** on any box hosting a CI runner. Order: sweep leaked test daemons by **exe-path scope** first (Phase B leaks ~6 `spt.exe` that hold the binary; CI reaps them, local rigs never do) → `rm` the target → **report before/after free bytes**.
2. **Audit your own Temp claude session scratchpads** for dead gate targets from past sessions and dispose them. **Your own only** — never another agent's. A live rig you cannot attribute is someone's gate mid-run.
3. **No multi-GB deletion while a timing-sensitive CI job runs on this box** — co-tenant I/O tips Phase B's timing asserts. Check `gh run list` for in_progress jobs on your host first; queue the delete behind it.

**Why:** the free-space floor is the only thing standing between a leaked target and a whole CI lane going red on an unrelated PR. Disposal-by-memory already failed its author.

**How to apply:** fold the audit into a natural work boundary, not a drop-everything. My own 2026-07-29 audit found `.worktrees/release-lane/target` = 8.67GB dead since before v0.45.0 shipped — the *release* lane accumulates the same debt a gate lane does, so this rule binds release drivers too (the lane stays; its target goes).

⭐ **A battery that crosses a context reset leaves a full target on BOTH sides.** Disposal-as-last-leg structurally misses the pre-clear half unless the across-commune carries the disposal debt forward. (Observed: the W2 two-key gate left 15GB + 13GB in two sibling sessions.)

## ⚖ WORKTREE disposal — the BOUNDED procedure (doyle 2026-07-30, narrowed by hertz)
⭐⭐ **CENSUS FIRST, and let SIZE set the scope.** Measure dirty-state + origin-reachability + **size** for every worktree before removing one. Measured 2026-07-30: `engine-room` = **8,144 MB**; all 37 others 13–18 MB. **One worktree was 93% of the problem** — a "dispose 19" list would have reclaimed ~320 MB while touching four peers' active work. Result: 8.0 GB reclaimed (`df -k` 140,710,256 → 149,071,796 KB), peers untouched.

Order, and every step is a precondition for the next:
1. **Census** (dirty · `git branch -r --contains` · `du -sm`).
2. **Capture uncommitted work** to a **gitignored** path — `git diff HEAD` **and COPY untracked files, never just list them**.
3. `git worktree remove` (`--force` only *after* step 2).
4. `git worktree prune`.
5. **`rm -rf` is a destructive FALLBACK, not a preference** (hertz's narrowing, accepted): permitted only when the remnant is already **deregistered**, the path is **exactly scoped**, and the content is **proven reachable on origin**. It can bypass worktree metadata and silently leave mixed state. Its useful property is that it *skips* what it cannot remove and completes, where `Remove-Item -Recurse -Force` **aborts the whole operation on the first locked entry** (measured: deleted nothing across four worktrees).
6. **Measure the remainder explicitly** — file count, dir count, free bytes. Never infer that a delete completed.
7. **Pinned remnants → recorded retry-later.** Never kill processes on a shared box to chase them.

⭐⭐ **Size is not identity.** Two captured patches were both exactly **3084 bytes** on the same file; `cmp` proved **different content**. Deduping on size would have destroyed one of two distinct pieces of work.
⚠ **Do not write a probe that WRITES to what it measures.** Hunting the pin, I ran `[System.IO.File]::OpenWrite("<entry>\.__probe")` piped to `Out-Null` — for directories that creates a file and **leaks the handle**, so every "PINNED" reading was my own footprint and every "free" reading was a thrown exception on an invalid path. Results discarded, 8 stray files cleaned. Worse than an instrument aimed at the wrong population: this one **modified the population**. See [[audit-the-boring-claims]].
⭐ **Falsify the self-blame too:** I suspected my own Bash tool (its cwd persists between calls and I had `cd`'d into the pinned path). Moved out, retried — still pinned, all five. A plausible self-blame left untested closes the question wrongly.
Open: 5 empty 3-dir skeletons pinned at `<worktree>/crates/spt-daemon` (`Device or resource busy`), unattributable without `handle.exe`; retry when the box is quiet.
⭐ **A FAILED classification command's empty output reads as "nothing found" (hertz, 2026-08-04, F1 rig teardown):** his first outbound/inbound classification ERRORED on a format string and returned NOTHING — read as silence it says "no reparse points", and the unclassified target gets deleted on the strength of a failed command. The verdict must come from a run that EXITED 0, never from empty output ([[verdict-from-exit-not-from-silence]] applied to the disposal leg). Also his: a file-length sum OVERSTATES reclaim when cargo hardlinks binaries (15.40 GB summed vs +13.24 GB freed; link count 2 on spt.exe) — treat the sum as an upper bound, and a free-space delta on a shared box as a snapshot, not a controlled measurement.

Related: [[gate-rig-mechanisms-not-remembered-steps]], [[gate-clean-target-not-incremental]], [[hfenduleam-disk-full-ci]], [[rule-failed-its-author-needs-a-mechanism]], [[no-machinewide-killon-shared-runner]], [[worktrees-under-project-root]], [[audit-the-boring-claims]], [[a-predicate-without-its-tool-is-not-evidence]]
