NS-015
documentedstatus-from-the-wrong-process
A pipeline returns the status of its last command, not its failing one
- reads as
- `npm test | tee build.log` exits zero and the log file is written. Conclusion drawn: the tests passed.
- actually
- A shell reports the exit status of the last command in a pipeline. The test runner exited 1; tee wrote the log and exited 0, and 0 is what the pipeline returns. `set -e` does not intervene, because the pipeline as a whole succeeded.
- blind because
- One number is produced for a chain of processes. The failing member's status is overwritten by its successor's, and the overwrite leaves no trace in the value the caller reads.
- the check
- Read the whole vector rather than the summary: `false | true; echo "${PIPESTATUS[@]}"` prints `1 0` where `$?` prints `0`. Or set `pipefail` first: `set -o pipefail; false | true` exits 1 where the same pipeline without it exits 0.
- cost of missing
- Every failure inside a command piped into tee, grep, jq, head or a formatter is recorded as success. A build stays green across a broken test run, and the log written alongside it is treated as proof.
- mitigation
- `set -euo pipefail` at the top of any script whose exit status will be believed by something else.
- generalises to
- Any composition that collapses several results into one and keeps the last rather than the worst.
- source
- gnu.org