Capture the exit status in a variable, not $? later

The pipe rule was only half of it. $? is a single slot that every
command overwrites, including the harmless one somebody adds months
later — a sed that redacts a token out of a log, a rotate, an echoed
heading. The command fails, the sed succeeds, and the check reads the
sed.

Measured both forms with a deliberately failing command and a sed
between: the bare $? reported SUCCESS on a command that had failed,
while a status captured into rc on the very next line still read 1.

So the durable form names it immediately and branches on the name,
which survives anyone later inserting a redaction step. That matters
most in exactly the scripts that redact a credential before printing
a log, because that redaction is the change that looks safest.

Same family as the pipe above: a status read one step too late is a
status about something else.
This commit is contained in:
doxa-maps-88655c70 2026-09-24 23:48:12 +00:00
parent c94b3cb4b5
commit 7f59035fb1

View file

@ -150,6 +150,31 @@ with **each other**, not with the source. The same shape bit a `git merge` in th
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.
### And capture the status into a variable, not `$?` later
`$?` is a single slot that EVERY command overwrites — including the harmless one somebody
adds months later for readability. Measured here:
```sh
some_command > log 2>&1
sed -n '1p' log # redact a token, rotate the log, echo a heading — anything
if [ "$?" -ne 0 ]; then ... # tests SED, not some_command. A failure reads as success.
```
The durable form names it on the very next line, and branches on the name:
```sh
some_command > log 2>&1
rc=$? # nothing may come between these two lines
sed -n '1p' log # now insert whatever you like
if [ "$rc" -ne 0 ]; then tail log; exit 1; fi
```
Both were run: with the `sed` in between, the bare `$?` reported success on a command that
had failed; `rc` still read 1. This is the same family as the pipe above — a status you read
one step too late is a status about something else — and it matters most in exactly the
scripts that redact a token before printing a log, because that redaction looks harmless.
## Landing on main: the shape of the lock
`main` is shared and serialized by `main-capture`. The ordering is load-bearing and BOTH