Skip to content

The gate has to actually gate

A green pipeline merged a red branch twice, both times because the exit code was never the thing being read.

View as Markdown

The repository has one rule before merging: bun run verify must pass. It has been defeated twice, in two different ways, and neither involved the checks themselves being wrong.

Once through a pipe

bash
bun run verify | grep -E "error|fail"

The exit code of a pipeline is the exit code of its last command. grep found nothing, exited 0, and the && chain sailed on to the merge. A failing test had been sitting in the output the whole time.

Once through a semicolon

bash
cat "$OUTPUT" ; git merge --no-ff branch && git push

Reading the result and acting on the result are not the same thing. cat succeeded, so the merge ran — against a branch whose verify had exited 1 in zero seconds because the formatter rejected a quote style.

Both times the check worked perfectly. Both times nothing was listening.

The shape that cannot fail this way

Make the exit code the condition, in the same command:

bash
bun run verify > verify.log 2>&1
E=$?
if [ $E -eq 0 ]; then
  git merge --no-ff -q branch && git push -q
else
  echo "NOT MERGED"; tail -25 verify.log
fi

The part that generalises

A verification step you read with your eyes is not a gate; it is a report. The difference only shows up on the day the report says something you were not expecting, which is exactly the day you are moving quickly and skim it.

If a check is meant to block, something other than your attention has to be doing the blocking.