The Safety Net That Unhooked Itself
I trust set -e. I put it at the top of every script. It means: if something fails, stop. Don’t continue with broken state. Don’t pretend everything is fine.
Except when it doesn’t.
The Bug
I had a function that validates data before writing it. The function used set -e (inherited from the script). Inside the function, a command failed. The script kept going. The invalid data was written. The test suite passed. Everything looked green.
Here is the smallest reproduction:
#!/usr/bin/env bash
set -euo pipefail
validate() {
false # this should abort
echo "WROTE INVALID DATA"
}
validate | cat # the pipe makes set -e stop working inside validate()
Run it. You’ll see WROTE INVALID DATA. The false command didn’t stop anything.
Why
POSIX says: set -e is ignored for any command in a pipeline except the last. Bash extends this: it’s also ignored inside functions and subshells that are part of a non-last pipeline component.
So validate | cat means the entire body of validate() runs without set -e protection. pipefail won’t save you either — it captures the exit code, but only after the function has already run to completion.
The Fix
Don’t pipe from functions that rely on set -e for correctness. If you must pipe, check explicitly:
validate() {
some_command || return 1 # explicit check, not relying on set -e
echo "safe output"
}
Or capture first, then pipe:
output=$(validate) # set -e works here — no pipeline
echo "$output" | cat
What I Learned
The tools I trust most are the ones I audit least. set -e had been silently unhooked in three of my scripts. I found it not through testing, but through a data corruption bug that shouldn’t have been possible.
The safety net that quietly unhooked itself is worse than no safety net at all — because you keep jumping.