---
name: a-leg-that-ends-in-a-grep-exits-with-the-grep
description: "A wrapper script whose last statement is a summary grep/echo returns THAT status, so the driver's `<leg>.exit` read 0 beside a.exit=100 b.exit=100 and the mutation arms ran on a red baseline. Gate the next leg on the role exit FILES, never on the wrapper's status; and on one box space rig ports so per-cell offsets cannot land on the peer's port."
metadata: 
  node_type: memory
  type: feedback
  originSessionId: 647c7dc2-d1c2-49f5-9513-98ffb742a36a
  modified: 2026-09-07T02:57:54.365Z
---

2026-09-07 02:53Z, W1 gate (`gate-w1.sh` -> `twohost-web-local.sh`): the pair wrapper ran both roles,
wrote `a.exit`/`b.exit`, then ended with `grep ... | sed` summary lines. Its process status was the last
pipeline's, so the driver recorded `LEG pair-none EXIT 0` while both role files read 100, its
`if [ "$(cat pair-none.exit)" = 0 ]` guard passed, and arm A started mutating the tree on a red baseline.
Killed by hand; the mutation reverted by hand (a killed wrapper never reaches its own revert step).

Same run, the red itself was a RIG defect: the test binds role A's cells at `port_a + 0..2` and role B at
`port_b`, both +20; my one-box script set 7460/7461, so cell 1 (udp 7481) bound ON B's broker (udp 7481)
and failed in 0.075 s with `endpoint bind failed`. The two-box run has no collision, which is why the
defaults never showed it.

**Why:** a wrapper's exit status is the cheapest thing to believe and the least likely to be the verdict;
the verdict is in the files it wrote. A guard on the wrong status does not fail loud, it lets the next
(mutating) leg run and produces void verdicts that look like mutation results.

**How to apply:**
- A wrapper that writes exit files ends with `exit $(max a.exit b.exit)` or the driver reads the files
  directly (`pair_ok` = both files exist AND read 0). Never gate on `$?` of a script that prints a summary.
- A killed wrapper leaves its cleanup undone: after any kill, `git status -uno` the tree and revert by hand.
- One-box pair rigs: read the port derivation in the test (`port_a + offset`, `port_b`) and space the
  bases by more than the cell count; write the reason beside the numbers.
- Related: [[never-send-a-claim-composed-before-its-check-ran]] (same shape: the `&&` guards one
  statement, the next line runs anyway), [[a-stopped-local-ssh-does-not-stop-its-remote-command]].

**THIRD AND FOURTH FACE, 2026-09-09 (releases#289, both mine, one hour apart).** Same rule, two
new carriers:

- **A pipe re-parents the verdict.** I ran `cargo test ... 2>&1 | tail -30`, so the status reported
  was TAIL's. The output ended `[exited with code 0]` printed directly UNDER
  `error: could not compile spt-daemon (lib) due to 2 previous errors`. A compile RED rendered
  itself as a green exit, and had I read the code instead of the text I would have handed a gater
  a pass on a tree that does not build. Doyle: second instance on that box that day (a driver had
  printed `build exit=0` off PIPESTATUS after a subshell). From then on: `cmd > x.raw 2>&1;
  echo "EXIT=$?" > x.exit` on every leg, no pipe anywhere near the verdict.
- **An exit FILE carries the clock it was written on.** The next run of the same command was still
  in flight (936/3142 tests) while the PREVIOUS, cut-short attempt's `EXIT=1` still sat in the file.
  Polling read that stale 1 as this run's verdict. A leg that ends mid-compile with no Summary and
  no error text is VOID, not red — and an exit file is only evidence once the run that writes it
  has gone terminal. Check the file's own recency (or delete it before the re-run), never just its
  contents. Kin [[a-stamp-typed-from-the-previous-turns-clock-is-a-guess]].
