### IR-79 — 22 of the 31 rig binaries leak their daemon tree on a failing assert: teardown is a statement, not a guard

- **Status:** OPEN, filed by hertz 2026-09-07 on doyle's dispatch during PR #198's CI window,
  from todlando's red 6 (a stale same-port daemon from his own previous run answering the next
  one). · **Origin:** PR #198 moved 31 rigs to `SPT_TEST_EPHEMERAL_ADVISORY_PORTS=1`. Under an
  ephemeral port a leaked daemon can no longer ANSWER the next run, so that PR is right as it
  stands and this entry is not a defect in it; what remains is the orphan process itself — it pins
  `spt.exe` (the Windows delete-and-rebuild hazard) and holds an `SPT_HOME` that nothing reaps. · **SCOPE BROADENED 07:20Z:** the title says "on a failing
  assert" because that is the face it was filed from; the SECOND FACE bullet below shows the same
  mechanism firing on a nextest TIMEOUT and leaking THREADS rather than a daemon. Read the title as
  the handle, not the boundary — the trigger is any exit that does not reach the teardown statement.
- **What/why:** measured over the 31, classifying each binary by whether its daemon teardown
  survives a panic (a failing `assert!` unwinds, so only a guard or an ordering discipline saves
  the child):
  - **panic-safe, 9:** `impl Drop` guard — `endpoint_autostart_e2e`,
    `knock_mutual_cross_node_e2e`, `twohost_cli`; `catch_unwind` teardown —
    `activity_link_push_e2e`, `attach_link_push_e2e`, `wake_resume_bind_e2e`;
    teardown-then-assert with zero exposed asserts — `endpoint_teardown_authority_e2e`,
    `er_briefing_session_scoped_e2e`, `er_sequestered_cwd_e2e`.
  - **leaks on a failing assert, 22** (exposed asserts / total asserts in the test body):
    `projindex_writer_e2e` 21/21, `projindex_reader_e2e` 18/18,
    `live_adapt_translation_swap_e2e` 15/38, `brain_split` 12/12,
    `er_briefing_presented_e2e` 10/28, `rc_attach_truth` 8/29, `brain_respawn_rename` 6/6,
    `dummy_harness_e2e` 4/11, `idle_edge_drain_e2e` 4/11, `multi_subnet_bringup_e2e` 4/20,
    `bind_honest_cross_perch_e2e` 3/6, `idle_edge_seal_e2e` 3/17, `resident_service_e2e` 3/24,
    `attach_wedge_e2e` 2/8, `bind_cwd_project_e2e` 2/8, `daemon_refresh_e2e` 2/11,
    `er_brief_once_per_session_e2e` 2/13, `n1_pairing` 2/5, `resume_template_e2e` 2/10,
    `run_no_dup_session_e2e` 2/14, `er_briefing_presentation_e2e` 1/4,
    `livehost_bootgate_e2e` 1/3.
  **FIRST WAVE of the Drop-guard generalization lane (doyle-ruled 2026-09-07, composed at the next
  register sweep, not now): `projindex_writer_e2e`, `projindex_reader_e2e`, `brain_split`,
  `brain_respawn_rename`.** These four expose EVERY assert they have — their teardown is the last
  statement in the body, so any red at all leaks, which makes them both the worst cases and the
  cleanest proofs that a guard works. The remedy already exists in this tree —
  todlando's `DaemonReaper`, a `Drop` guard armed BEFORE the first CLI call — and the follow-up
  lane is to generalise it into `crates/spt/tests/common` and adopt it at these 22 sites, which is
  also the only shape that covers a `SIGKILL`-free timeout kill by nextest.
- **METHOD, and its limits, so the count can be re-derived and challenged:** the classifier reads
  each `#[test]` body, resolves file-local helper fns whose own body tears down (so a teardown
  called through `sweep()` counts), and reports every `assert!`/`panic!` positioned before the LAST
  teardown call in that body. It therefore (a) misses asserts written inline inside a closure or a
  macro argument rather than at statement position, (b) treats the last teardown as THE teardown —
  a partial earlier teardown still leaves the tree, so the true exposure is >= this count, and
  (c) does not model `?` or early `return`. Spot-verified by hand on `resident_service_e2e`, whose
  3 exposed asserts are real: they fire in the `wait_until` legs well before the `sweep()` at :435
  that the "── ASSERTIONS ──" block follows.
- **TONIGHT'S LIVE SAMPLE, so this is not an academic count (doyle's hfenduleam census 06:43Z):**
  10 leaked `spt.exe` from todlando's pool, pairing by start time, from his pre-yield legs — a
  single evening's rig work on one box. Each pins the binary against a rebuild and holds an
  `SPT_HOME` nothing reaps.
- **SECOND FACE — the same hazard in THREAD clothes, and it blocks the RUNNER, not just the box
  (measured by todlando 07:17Z, relayed by doyle 07:20Z; I did not measure it myself and record it
  as his testimony):** a nextest TIMEOUT on a `twohost_web` cell left the cell's child process
  holding the broker + listener THREADS. Consequences, with his numbers:
  - nextest itself blocked **14 minutes** at **0.61 CPU-seconds** — a wall-clock hang with
    essentially no CPU, which is the signature of a parent waiting on a child's pipe rather than
    of work being done;
  - **`a.exit` was never written**, while **every verdict was already in `a.raw`** — so the leg
    read as INCOMPLETE at the exact moment its results were complete. A reader who trusts the
    exit file over the raw would call this a hung or failed leg and rerun it;
  - **killing the path-verified orphan let the parent finish at once** — which is the causal test,
    not a correlation: the kill is the intervention and the unblock is the response.
  **Why it belongs in THIS entry rather than a new one:** the leaked thing is a thread inside a
  child, not a daemon, but the mechanism is identical — a teardown that is a STATEMENT does not run
  when the body does not reach it, and a timeout kill reaches the body even less reliably than a
  panic does. The `Drop`-guard lane covers both faces with one remedy, which is doyle's ruling and
  the reason no separate entry is opened. It also raises the lane's value: the daemon face costs a
  pinned binary and an unreaped `SPT_HOME`, this face costs **14 minutes of a serialized golden
  leg** and manufactures a false INCOMPLETE.
  **Reading rule this hands the gate, worth stating because it is cheap:** when a leg's `.exit` is
  missing, read the `.raw` for a Summary BEFORE concluding the leg hung — kin to
  [[a-stopped-local-ssh-does-not-stop-its-remote-command]], where the raw was likewise the honest
  record and the wrapper was not.
- **Kin:** FLAKE-LEDGER `resident_service_e2e` :664 teardown LEAK row (3 occurrences, the Windows
  face of the same class), [[IR-34]], [[IR-74]] (kitsubito's 21,643 `/tmp` endpoint homes — this is
  one of the producers), and todlando's red 6.
- **Ripe when:** the next lane that touches `crates/spt/tests/common` — the guard belongs there,
  not copied 22 times.
- **Size:** medium — one guard in `common`, then 22 mechanical adoptions, each provable by
  panicking the body under a temporary test and watching the census go quiet.
