Write down that a pipe hides a failure

A pipeline's exit status is its last command's, so a build piped
through tail reports success when the build failed — and the chain
runs on, publishing a stale bundle with new source and an old .js.
Every gate stays green afterwards, because the SRI check proves the
leaf and the bundle agree with each other, not with the source.

Measured all three forms on this machine rather than repeating the
warning: the piped form reports success on a deliberately broken
build, set -o pipefail catches it, and redirecting instead of piping
catches it because the status is then the build's own.

The same shape already bit a merge here: "if ! git merge ... | tail"
tested the pipe, so a conflicted merge read as clean and the
resolution step was skipped.
This commit is contained in:
doxa-maps-88655c70 2026-09-24 21:35:09 +00:00
parent 558786ddf7
commit e680a9cde9

View file

@ -125,6 +125,31 @@ owe a complete audit.
★ **Read the image when the deliverable is visual.** Numbers have certified visually broken output
in this repo more than once — a green generator is not a look-at-it.
## A pipe hides a failure
A pipeline's exit status is its LAST command's. So this publishes a STALE bundle — new
source, old `.js` — and says nothing:
```sh
BUNDLE=x bun x vite build 2>&1 | tail -2 && bun run tree # tail succeeded, so && proceeds
```
Measured on this machine, all three states:
| Form | On a failed build |
|---|---|
| `build 2>&1 \| tail -2 && next` | **reports success** and runs `next` |
| `set -o pipefail` before it | stops, correctly |
| `build >/dev/null 2>&1 && next` | stops, correctly — no pipe, so the status is the build's |
Every gate stays green afterwards, because `check:sri` proves the leaf and the bundle agree
with **each other**, not with the source. The same shape bit a `git merge` in this repo:
`if ! git merge … \| tail` tested the pipe, so a conflicted merge read as clean.
**Rule:** never put a build or a merge on the left of a pipe you then branch on. Redirect
instead of piping, or `set -o pipefail` first. If you want short output AND a reliable
status, capture to a file and `tail` the file afterwards.
## Landing on main: the shape of the lock
`main` is shared and serialized by `main-capture`. The ordering is load-bearing and BOTH