---
name: exit-code-after-a-pipe-is-the-tails
description: "`cmd | tail -N; echo $?` reports TAIL's status, not cmd's — and the same tail hides the earlier test-summary section, so a gate can read green twice over"
metadata: 
  node_type: memory
  type: feedback
  originSessionId: 023c684f-80dc-42c0-80fc-fd489a150e2b
  modified: 2026-08-04T02:08:46.006Z
---

**Measured 2026-08-03 (todlando), gating a lane before pushing to main.** I ran
each gate leg as `cmd 2>&1 | tail -8` and then `echo "EXIT=$?"`. Two independent
faults in one line:

1. **`$?` after a pipeline is the LAST element's status.** `EXIT=0` was `tail`
   succeeding. `traceable-reqs check` could have exited 1 and the line would
   still have printed `EXIT=0` — a success word printed on the same screen as
   its own failure output ([[render-not-read-pipefail]],
   [[hedging-classification-is-not-grounding-the-observation]]).
2. **`tail -8` hid the FIRST test binary's summary.** `cargo test -p xtask`
   emits one `running N tests` / `test result:` block PER BINARY; the tail kept
   only the last, so a 39-test section vanished and the run read as "4 tests".
   The count is the tell for a stale/false green
   ([[shared-target-stale-false-green]]) — and the instrument was deleting it.

**Do instead:**
- capture the exit directly — `cmd > out.txt 2>&1; echo "exit=$?"` — then grep
  `out.txt`; or read `${PIPESTATUS[0]}` in bash, naming which element you mean
- grep for the STRUCTURE you need rather than tailing: `grep -E "^(running|test result|error)"` returns every binary's block
- for counts, assert the expected number before reading the verdict
  ([[nextest-zero-match-filter-trap]], [[nextest-singular-summary-parse]])

**Why this is a rule and not a slip:** both faults produce a GREEN. Nothing in
the output looks wrong, so there is no moment where you are prompted to check.
I caught it only because I re-measured before reporting — the discipline of
[[audit-the-boring-claims]] applied to my own instrument. Catching it BEFORE the
report is what the discipline is for; a gate whose failure signature is
indistinguishable from its success is [[instrument-soundness-guards]].

## Recurrence 2026-08-21: it cost a FALSE ISSUE, and it escaped this file's own trigger

Measured while answering todlando's question about my #153 filing. On 2026-08-04 I ran, on HFENDULEAM
at 21:44:32Z and over ssh to KITSUBITO at 21:43:16Z:

    spt update --restart 2>&1 | tail -20; echo "=== EXIT=$? ==="

and filed #153 ("a structural refusal exits 0") off the resulting `EXIT=0`. Source returns **3** and
always did — verified at v0.50.0, v0.52.0, v0.53.0 and c62904e7, on both the verb and the composite.
The marker measured `tail`. The issue sat 17 days and consumed the front of a builder's lane before
todlando asked how the exit code was captured instead of reinterpreting my transcript.

**Why knowing the rule did not fire the rule.** This file is indexed under GATE-TEST-INDEX, and my
⭐⭐ trigger for that sub-index is "first touch of a gate/rig/test-filter/CI log". A fleet roll is none
of those. The trap is not gate craft — it is **shell-capture craft**, and it fires anywhere a verdict
is read from a hand-built command line: fleet rolls, field probes, one-off ssh checks, anything whose
output you intend to quote in a ticket. Filing a mechanism under the activity where you first met it
puts it out of reach of every other activity that can hit it. **Trigger, restated: any time an exit
status becomes EVIDENCE — not just inside a gate.** If a number is going into a ticket, a report to a
gater, or a ledger, capture it without a pipe (`cmd > f 2>&1; rc=$?`) or set `pipefail` first.

**The tell I wrote into my own ticket.** #153's "Contrast" paragraph reported a SUCCESSFUL roll on
another node also exiting 0, and offered it as corroboration that "exit 0 covers both rolled and
refused". That is the refutation, filed as supporting evidence. See
[[identical-readings-across-opposite-outcomes-indict-the-meter]] — the general form, which is the
durable half of this incident. Blast radius bounded afterwards: `grep -rn 'EXIT=$?'` empty across
committed `*.sh`/`*.ps1`/`*.yml`/`*.md`, and every `.github/ci` script plus `ci.yml` sets `pipefail`,
so this never reached CI or the release lane's automated path. **Containment is stronger than that
first sweep said** (doyle corrected it in my favour, then I measured the complement): `golden.yml`
sets `pipefail` in 10 places and `release.yml` in 2, so all three workflows are covered, including
the release lane's OWN automated path -- the surface where this class would have been worst.
`release.yml`'s low count is not a gap: its POSIX pipes are at 74, 75, 138, 139 and all four sit
inside the two `pipefail` blocks (72, 136); its third bash block (162) has no pipes. **Scope the
claim to POSIX, or a re-runner will "refute" you:** a broader pattern finds a FIFTH pipe at 119,
`New-Item -ItemType Directory -Force dist | Out-Null`, inside a `shell: pwsh` block (doyle caught
this). It is not an unguarded pipe -- `pipefail` is a POSIX concept that does not exist in
PowerShell, which propagates via `$ErrorActionPreference` / `$LASTEXITCODE`, and that line pipes a
CMDLET, so no exit status is in play at all. Different language, different rules, outside the class.
The honest sentence is "every POSIX pipe in this file is under `pipefail`, and the one PowerShell
pipe is outside the class entirely" -- a containment claim must name the LANGUAGE it holds for, or a
reader re-running the check with a wider regex gets 5-vs-4 and reads sloppiness where there is none.

**Generalised (doyle took the cmdlet point over his own and carried this form):** the error shape is
ANY claim of the form "all X are guarded" made without naming the domain X ranges over. That is how a
TRUE statement about POSIX pipes becomes a FALSE-LOOKING statement about pipes. Name the domain in
the sentence, not in your head. Two independent grounds put 119 outside the class -- no `pipefail` in
PowerShell, and nothing for it to guard anyway since a cmdlet pipeline carries no native exit status
-- and a claim is stronger stated with the ground that survives the other being wrong.

**On whose lapse it was:** doyle volunteered that he made the same guard-vs-complement error the same
afternoon (reporting golden.yml's 10 occurrences as containment proof), and said explicitly why --
"a rule that reads as one person's lapse gets remembered as that person's lapse", and this one will
bite whoever counts a guard next. Write a shared shape as a shape. Attributing it to one agent buries
it in that agent's file, where the next person to make it will not look. **Method note: counting `pipefail`
occurrences measures the GUARD; what settles containment is the complement -- are there pipes NOT
under one.** A low guard count on a file with no pipes is correct, and a high one proves nothing on
its own.

### Refinement: TRUNCATION pipes are where this fires, and the correct form is already known

Swept for other producers of the marker afterwards (2026-08-21). No hook, script, or shared wrapper
emits `=== EXIT=` — it is always authored inline per command, so the radius is hand-written calls
only. But peer transcripts from 2026-07-07 (spt-mobile) carry BOTH forms, and the split is the
lesson:

- `./gradlew ... 2>&1 | tail -25; echo "EXIT=${PIPESTATUS[0]}"` — correct, on a TEST VERDICT
- `spt endpoint digest ... 2>&1 | head -c 4000; echo "=== EXIT=$? ==="` — flattened, on a DISPLAY call

The right idiom is known and gets reached for when the exit status is obviously the point. The broken
one appears on **truncation** pipes — `| head`, `| tail`, `| head -c` — because those read as
*formatting*, not as pipeline elements, so the "what is the last element of this pipeline" question
never gets asked. That is exactly the shape my #153 capture used, and truncation is what you add when
you are trimming output in order to QUOTE it. So the idiom fails hardest precisely where the number is
about to become evidence in a ticket or a report.

**Rule:** `| head` / `| tail` for readability still makes it a pipeline. Either capture first and trim
after (`cmd > f 2>&1; rc=$?; tail -20 f`), or use `${PIPESTATUS[0]}`. (Not claiming those peer runs
were wrong — a display call's status may not have been load-bearing; the PATTERN is the finding, and
it was measured from transcripts, not from re-running anything.)

### Why it self-conceals: a flattened exit AGREES with the truth nearly always

flynn, measured 2026-08-21 in a live session (his measurement, relayed with source). He hit both forms
on the SAME tool within minutes:

- correct: `traceable-reqs check 2>&1 | grep -v ... | tail -20; echo "EXIT=${PIPESTATUS[0]}"`
- flattened: `... | grep -v "\[OK\]" | tail -15; echo "EXIT=$?"` — reported `EXIT=0`, and he relayed
  "Baseline green (328 tests, 125 reqs, exit 0)" to the operator on the strength of it

Same tool, same session, same author; split by nothing but whether the pipe read as formatting. The
flattened one was the truncation pipe, reached for because he was trimming a long verdict IN ORDER TO
QUOTE IT — the refinement above, reproduced independently the same day.

**The part that makes this dangerous:** his CONCLUSION was right and his EVIDENCE was invalid. He
re-ran capture-first and the gate really was green (traceable-reqs exit 0, 128/128; cargo test exit 0,
336 passed), so the shipped claim stands — but it stands because the gate was green, not because the
cited number proved it. **A flattened exit agrees with the truth nearly always**, which is exactly why
nobody audits the idiom. My #153 is the rare case where it DISAGREED, and that disagreement is the
only reason the trap surfaced at all. Invert the risk model accordingly: the hazard is not frequent
wrong conclusions, it is a near-perfect agreement record that keeps the instrument from ever being
questioned, until the one time it matters and produces a false ticket that eats a builder's lane.

Same family, other direction (flynn, same session): an `&&` chain broke because `grep -c` exits **1**
on zero matches, and zero was the SUCCESS case — he was counting orphan log lines. Pipeline-element
status misread as a verdict, inverted.

**Standing craft, adopted on both sides:** capture-first for anything that becomes evidence —
`cmd > f 2>&1; rc=$?` then trim `f`. `${PIPESTATUS[0]}` where a pipe is genuinely wanted.


**A false ticket can still be aimed at a real class.** doyle's census during the CUT found the
instance one layer down: `ADAPTER_UPDATE_REFUSED` returns `AdapterUpdateOutcome::Failed`, so the F-5
floor gate -- whose whole purpose is leaving the live install byte-untouched -- reports its refusal
as a FAILURE. Minted as #213. So #153's *instinct* (a refusal that performs no work should not be
indistinguishable from the other outcomes) was sound; the meter was wrong about where and about the
evidence. Retracting a filing's EVIDENCE does not automatically retract its SUSPICION -- say which
you are withdrawing.
