DUNIN7 · LOOMWORKS · RECORD
record.dunin7.com
Status Current
Path standing-notes/loomworks-standing-note-a-gates-exit-code-must-never-be-read-through-a-pipe-v0_1.md

Loomworks Standing Note — A Gate's Exit Code Must Never Be Read Through a Pipe — v0.1

Version. 0.1 Date. 2026-08-26 Status. Standing note for the Loomworks project. Read before writing or running any command chain that gates a commit or push on a check's result. Applies to every gate in the pre-push sweep — ruff, the mypy ratchet, pytest — and to any future one. Origin. CR-2026-241 (B-108), 2026-08-26. Caught in-session on the post-merge re-run, fixed within minutes at 72e4499.


1. The rule

A gate's exit code must never be read through a pipe. In a shell pipeline, && and set -e see the exit status of the last command in the pipe, not the one whose result you meant to gate on. Piping a gate into tail, grep, head, or anything else silently discards the gate's own answer and substitutes the pipe stage's — which almost always succeeds.

A green-looking sweep is not a green gate. The terminal output can show a clean, truncated summary line while the command that produced it exited 1. Nothing about the visible text distinguishes the two cases; only the actual exit code does, and the pipe threw it away.

2. The instance

The mypy ratchet (scripts/check_mypy_baseline.py) exits 1 when a new, non-baselined error appears (raise SystemExit(main()) — confirmed by reading the script). The command run in-session for CR-2026-241 was:


uv run python scripts/check_mypy_baseline.py 2>&1 | tail -1 && git add -A && git commit -m "..." && ...

The ratchet had one new error at that moment (held_assent.py:154, an unnarrowed Optional access bool(data) and data.get(...)). The piped tail -1 printed the ratchet's own failure message — which read as informative, not alarming — and then exited 0 regardless, because tail had successfully read its input. && saw tail's success and proceeded straight through git add, git commit, and the push. The commit that carried a red gate looked, on screen, exactly like every green one before it.

Caught only because the ratchet was re-run standalone, unpiped, immediately after the push — not because the sweep itself said anything was wrong.

3. Why this is the same family as the literal-pinning note

loomworks-standing-note-a-test-that-pins-a-literal-cannot-follow-a-ruling-v0_1 names a check that changes shape without anyone deciding it should. This is the sibling failure at the shell layer: a check that cannot fail is not a check. The ratchet script itself was correct and did its job — it computed the right answer and exited with it. The failure was entirely in how its answer was read. Same as loomworks-standing-note-observe-the-failure-before-trusting-the-check-v0_*: a check that never gets the chance to fire looks identical to one that fired and passed, until you go looking for the difference on purpose.

4. The correct forms

Any of these restore the gate's exit code to visibility:

Preferred — run the gate unpiped, report separately.


uv run python scripts/check_mypy_baseline.py

Read the full output if you want it; if you also want a short summary, capture to a variable or a file and inspect that, never pipe the live command:


uv run python scripts/check_mypy_baseline.py > /tmp/mypy.out; ec=$?
tail -1 /tmp/mypy.out
[ "$ec" -eq 0 ] || { echo "RATCHET FAILED"; exit 1; }

set -o pipefail, when a pipeline is unavoidable. Makes $? (and therefore &&/set -e) reflect the last command to fail in the pipe, not merely the last command:


set -o pipefail
uv run python scripts/check_mypy_baseline.py 2>&1 | tail -1 && git commit ...

Note this is a shell option, not a per-command flag — it must be set in whatever shell context the pipeline runs in (a script's header, or before the line in an interactive/tool-invoked shell), and it silently does nothing if the invoking shell doesn't honor it.

PIPESTATUS (bash-specific), when you need the individual stage's code explicitly.


uv run python scripts/check_mypy_baseline.py 2>&1 | tail -1
[ "${PIPESTATUS[0]}" -eq 0 ] || { echo "RATCHET FAILED"; exit 1; }

Never acceptable: piping a gate into anything and chaining on &&/set -e with no pipefail and no PIPESTATUS check. That is the CR-2026-241 shape exactly.

5. What this asks going forward

  1. Every gate in a push sweep — ruff, the mypy ratchet, pytest, any future one — runs unpiped, or under pipefail, or with its PIPESTATUS checked explicitly. No exceptions for "just want a short summary."
  2. A truncated or filtered view of a gate's output is fine to print for readability. It must never be the thing the exit-code decision is made from.
  3. When a gate's failure is caught late (post-push, like this one), the fix is filed as its own small commit naming the miss — not folded silently into the next unrelated change — so the record shows the gate actually failed once, briefly, and was caught. CR-2026-241's follow-up commit (72e4499) did this correctly and is the model to repeat.

DUNIN7 — Done In Seven LLC — Miami, Florida Loomworks Standing Note — A Gate's Exit Code Must Never Be Read Through a Pipe — v0.1 — 2026-08-26