---
name: verdict-from-exit-not-from-silence
description: "BINDING self-rule: never take a command's verdict from grepped output through a pipe — the pipe discards the exit code and 'no matching lines' is not success. Capture the exit explicitly."
metadata: 
  node_type: memory
  type: feedback
  originSessionId: c612b2ce-ba9b-48b9-87cf-3e8b1816e2ea
  modified: 2026-07-30T02:36:31.689Z
---

Never report a command as passing because a grep of its output printed nothing. **Grep the output for DETAIL; take the VERDICT from the exit code.**

Caught in my own W3 gate evidence (2026-07-22): I reported "clippy --workspace --all-targets clean" several times, having run

```
cargo clippy --workspace --all-targets 2>&1 | grep -E "^(error|warning)" | head -5
```

That is a test for the ABSENCE of matching lines, and the pipe discards clippy's status entirely. If clippy aborts for a reason that prints no `^error` line — target locked by a leaked exe, disk exhaustion, link failure — the grep prints nothing and I report "clean".

**Why:** absence of matched output is not evidence of success. This is the same defect as [[gate-rig-mechanisms-not-remembered-steps]]'s LNK1318 bite (a battery leg piped to `tail` inside a `&&` chain masked exit-101, so a dead build read as leg-reachable) and the same shape as the vacuous-oracle class — a verdict that can be produced by the thing not running is not a verdict.

**Which claims survive this and which don't** — the distinction is the useful part:
- **Sound:** anything quoting a POSITIVE harness statement. `test result: ok. N passed; 0 failed` is emitted by the harness; a suite that never ran prints no such line, so there is nothing to quote. Same for `xtask check: OK`.
- **Sound:** anything where the exit was captured explicitly (`...; echo "EXIT=$?"`).
- **NOT sound:** anything whose evidence is "the grep found no errors".

**How to apply:** `cmd; echo "EXIT=$?"` as a separate statement, or `set -o pipefail`, and never let a pipe be the last thing in a chain whose success you intend to report. When reporting gate evidence, prefer quoting the tool's own positive verdict line over asserting the absence of failures. If neither is available, say the check is UNJUDGED rather than passing.

**SECOND SURFACE — the WATCHER (doyle's own, PR #66 rerun, 2026-07-23):** a run-level CI watcher looped `gh run view --jq -r '...'` — the stray `-r` made gh error "accepts at most 1 arg(s), received 2" every iteration, so the watcher was INCAPABLE of ever reporting the condition it watched, and its quiet read as "still running". Recovery was this rule's prescription: verdict from a direct `gh run view` exit + JSON, never from the watcher going silent.

**The generalisation both surfaces share (todlando's framing, doyle-adopted): an instrument that cannot fail LOUDLY cannot be trusted to report success — the WATCHER needs its own liveness/capability proof as much as the thing watched.** Same shape as the oracle-rig capability probe ([[absence-needs-sibling-probe]]) and the clean-FIN stop condition in `transport_death_eof.rs`: an instrument proves it CAN see the event before its silence about that event counts.

**How to apply (watcher form):** emit a positive heartbeat line per iteration (parsed STATE, not raw errors); treat the watcher's own nonzero exit as watcher-failure to SURFACE, never as loop-continue; and when arming, verify the FIRST iteration produced a parsed reading before trusting the loop.

**THIRD SURFACE (todlando's quiet-window detector, 2026-07-27):** detector v1 logged only on state CHANGES, so a busy box produced silence — and when the process was killed at 13:38Z its log was indistinguishable from "still watching a busy box". He hit this the same night he wrote the sweep-watcher lesson down, then rebuilt the trap in a fresh script an hour later — [[rule-failed-its-author-needs-a-mechanism]] in the wild. v2 mechanism (adopt): log EVERY poll, so **a stale last-line timestamp is ITSELF the death proof** — liveness becomes checkable from the artifact alone, no out-of-band process probe needed.

**FOURTH SURFACE — THE INVERSE, and it limits this rule (chert, 2026-07-29): exit 0 from a command that did nothing.** `sed -i` stages its edit through a temp dir; with that dir unavailable it exits **0** and writes NOTHING. Two consecutive repair attempts of mine "succeeded" while the 8 files were untouched. So the exit code is the right place to take a verdict *about whether a command failed*, and NOT sufficient for a verdict *about whether an effect landed*. Caught only because I counted the affected files before and after (`pre=8, post=8`) instead of reading one back and eyeballing it — the same falsifiable-count discipline that proved a merge resolution the same night (605 = 601+3+1, where the wrong resolutions would have read 604 or 602).

**How to apply (effect form):** for any command whose purpose is to MUTATE something, assert the mutation with a count or hash, not the exit status — and prefer a tool that cannot half-succeed. A transient environment fault makes this mandatory rather than optional, since an earlier passing probe does not license trusting the next call ([[tool-claim-must-name-its-environment]]).

**FIFTH SURFACE — exit 0 having MEASURED nothing, i.e. the inverse applied to a MEASUREMENT rather than a mutation (todlando, 2026-07-30).** My generated-label census harness opened with an unguarded `WORK="$(mktemp -d)"`. Under the transient `/tmp` outage that is not a crash: `WORK=""` ⇒ every `"$WORK/<set>"` collapses to `/<set>` ⇒ writes fail ⇒ **`n=0` printed for all six sets, exit 0** — with *correctly generated* labels attached, which is exactly what would have made the zeros credible. A harness built to stop a label overstating its predicate would have understated it to zero and looked MORE trustworthy doing so. ⚖ **A rig that cannot express its own failure is the same defect as a gate that cannot express its own impossibility** (doyle). Fixed with two fail-closed guards — `mktemp` failure ⇒ FATAL exit 2; any set file never written ⇒ FATAL `"rig failure, NOT a zero result"`. **Distinguishing "measured zero" from "failed to measure" IS the harness's job.**
⚠ And I nearly reported the negative control's exit as **0** because I piped it to `tail` — third time in one night, *inside the verification of the harness whose entire purpose is refusing verdicts read off the wrong source*. `cmd | tail; echo $?` is a lie every time.

⭐⭐ **MAKE A NEW RIG RED ON PURPOSE BEFORE TRUSTING ITS GREEN** (chert's rule, doyle-adopted fleet-wide). His gate-extractor earned trust by printing `FAIL(case6)` on 8 of 8 files when his awk escape broke, and it carries a synthetic control. **Every instrument that betrayed us that night — the census, the filters, the probes, the greps — had never been observed to fail.** So: negative control first, and state what it printed. Mine: `TMPDIR=/definitely/not/here` ⇒ REAL exit 2, stderr naming the cause, and **zero SET lines** — it cannot emit a plausible zero.

⭐⭐ **READ YOUR OWN ARTIFACT AGAINST YOUR OWN MOST RECENT RULING BEFORE PUBLISHING.** doyle enforced a retraction on a peer (don't decrement 7→6 to preserve arithmetic) and published that exact arithmetic repair *in the same message that praised the refusal*. I caught it by comparing two of his statements to each other — **no box, no rig, no source read, no measurement.** A same-message contradiction is evidence, and it is the cheapest evidence available; nobody ran this check all night until it was needed.

⭐ **SHA-PINNED vs TIME-VARYING** (deployah's decomposition, correcting my framing): "nothing in a test result touches a blob, the lockfile, the counter or the date" is true of the RUN and therefore **true and incomplete** — the counter and the date drift because of the releases repo and the wall clock, which move whether or not anyone touches the tree. Classify every check before claiming a green cannot move it, and **re-READ time-varying inputs rather than re-citing an earlier decode.** Corollary I now own as a named leg: after an ff-push, `git merge-base --is-ancestor <sha> origin/main` must exit 0 **read from a freshly fetched origin/main, not local remote-tracking** — that catches a push that exits 0 having silently not landed, the same species as `sed -i` exiting 0 having written nothing.

**SIXTH SURFACE — "PRINT THE COUNT, NOT THE INTERPRETATION" IS NOT THE FIX. (hertz correcting deployah, chert and me, 2026-07-30 — within minutes of all three of us adopting it.)**
The corrective arose from a real defect: deployah printed the hardcoded label `(empty = nothing of mine unpushed)` **underneath ten lines of output**, and chert printed `(no non-terminal runs above = clear)` from a label written before the command ran. chert's diagnosis of the class is exact and worth keeping: **a label placed by the author encodes the EXPECTED result, so it survives exactly the case it exists to catch** — the prose form of reading an exit code for whether an effect landed. Both adopted "print the COUNT, never the interpretation — `n=$(cmd | wc -l)`; a count cannot disagree with itself." **I adopted and banked it too. It is false, twice over:**
- **(a) A pipeline masks upstream failure into zero.** My own probe was `n=$(git log --oneline --branches --not --remotes 2>/dev/null | wc -l)`. On any git failure that prints **`n=0`** — so the corrective adopted to stop me claiming "nothing unpushed" from memory would have MANUFACTURED that exact false claim, **with a number attached, which reads as more authoritative than the prose it replaced.** A count inherits the credibility of measurement without inheriting the measurement.
- **(b) A perfect count can quantify the wrong population.** Three the same night, all arithmetically correct and all about the wrong thing: deployah's file-wide `TRUST` grep (109.5KB, scanning the file when the claim was about requirement IDS), my unanchored `proc\.rs` (2, when anchored `/proc\.rs$` gives 1), doyle's `gh release list` on `spt-bs-core` (empty — a repo that has never held a release).
✅ **CORRECT FORM: capture and GUARD the subject command's exit BEFORE counting, then name the predicate and the population beside the number.**
```
raw=$(git log --branches --not --remotes --format='%H'); rc=$?   # no 2>/dev/null
[ "$rc" -ne 0 ] && { echo "MEASUREMENT FAILED — not reporting a count"; exit 1; }
echo "POPULATION: commits on local branches not on any remote, this checkout = $(printf '%s\n' "$raw" | grep -c .)"
```
⭐⭐ **The shape worth carrying: deployah's corrective and mine were both "replace the prose with a number", and the number is just a shorter place for the same defect to hide.** The fix is never the output FORMAT — it is guarding the exit and naming the population. This is why the rule belongs here and in [[a-predicate-without-its-tool-is-not-evidence]] rather than as a style preference.
⭐ hertz also made me split a bucket I had collapsed: "7 commits with no line-anchored trailer" was really **1 EMBEDDED-malformed + 7 genuinely absent** (24 anchored, sum 32). **Different defects, different fixes** — do not treat "failed the probe" as one class before checking why each member failed it.

Related: [[a-predicate-without-its-tool-is-not-evidence]] · [[gate-rig-mechanisms-not-remembered-steps]] · [[rule-failed-its-author-needs-a-mechanism]] · [[absence-needs-sibling-probe]] · [[ground-dont-assume-on-incidents]] · [[tool-claim-must-name-its-environment]] · [[daemon-lifecycle-progress]]

**SEVENTH SURFACE — a MISSING exit RECORD is not a verdict either, and here the positive line and
the exit file disagreed in the direction this entry does not warn about (hertz, W2 gate, adopted by
doyle 2026-09-07 07:22Z).** todlando's leg had **no `a.exit` at all** while **every verdict was
already in `a.raw`** — a `twohost_web` cell timed out, its child kept the broker/listener threads,
and nextest blocked 14 minutes at 0.61 CPU-seconds waiting on it
([[a-nextest-timeout-does-not-kill-the-cells-children]]). Read exit-file-first, that leg is HUNG or
INCOMPLETE and the remedy is a rerun — a rerun of a leg whose results were complete.

**Why this is a qualification and not a contradiction:** this entry's own preference ordering was
already right and I had been reading it too narrowly. It says the SOUND evidence is *the harness's
own positive verdict line* (`Summary [...] N tests run: N passed`), and that an exit is the place to
take a verdict *about whether a command failed*. An exit file that was never WRITTEN is neither —
it is silence, the same species as "the grep found no matching lines", and I had been treating its
absence as informative because an exit file feels like a record rather than like a silence.

**How to apply (gate form, now binding on the W2 gate):** when a leg has no `.exit`, read the
`.raw` for a Summary BEFORE concluding the leg hung or failed. If the Summary is there, the leg's
results are complete and what is missing is the WRITER, not the work — diagnose the writer (CPU-time
of the runner tree near zero ⇒ waiting, then census orphaned children by path under the lane's pool)
instead of rerunning. Keep the ordering straight: `grep -c Summary` first for the VOID check (two
Summaries in one leg's raw = the window is void,
[[a-stopped-local-ssh-does-not-stop-its-remote-command]]), then read the one Summary as the verdict.
