A deploy script stopped deploying, and the reason was printed directly under the error message the whole time. The gate said this:
it answered but the body does not say FetchDue. Title:
<title>Invoice chasing software for agencies ... FetchDue</title>The string it had just failed to find is in the line beneath the complaint. The probe was one line:
set -euo pipefail
is_fetchdue() { curl -s "http://localhost:$1/" --max-time 45 | grep -qi "FetchDue"; }The failure is caused by the match
grep -q exits 0 the instant it sees the first match, and then it closes the pipe. curl is still writing the rest of the document into a reader that no longer exists, so curl exits non-zero. set -o pipefail makes the pipeline take the last non-zero status in it, which is curl's. The probe returns false, and it returns false precisely because the match was found early.
The size dependence is what makes this age into existence rather than show up in review. If curl finishes writing before grep is satisfied, nothing breaks. I reproduced both halves against a local http.server with the target string on line 1:
| probe form | 300-byte page | 520,066-byte page | |
|---|---|---|---|
| `curl -s ... \ | grep -qi under pipefail` | exit 0 | exit 23 |
body=$(curl -s ...); grep -qi <<<"$body" | exit 0 | exit 0 |
Exit 23 is curl's write error. The commit note records exit 56 for the same probe against the live host, where the transfer dies at a different layer. Neither is zero, which is all the shell cares about.
The live marketing page measures 357,757 bytes right now. It used to be small, and the probe used to work.

Invoice management interface displaying outstanding, unpaid, and upcoming invoices with amounts and dates — Financial metrics and complete invoice records with customer names, amounts, and payment status visible.
The fix is to stop making it a pipeline
is_fetchdue() {
local body
body=$(curl -s "http://localhost:$1/" --max-time "$PROBE_SECS" 2>/dev/null) || return 1
grep -qi "FetchDue" <<<"$body"
}Command substitution runs curl to completion, so its status is checked on its own terms. The here-string feeds grep from a buffer nobody can hang up on, and $? for the function belongs to grep. The two exit statuses stop being averaged together by a construct that was never asked to arbitrate between them.
Anywhere pipefail meets a short-circuiting reader, this applies. head, grep -q, grep -m1, read, an early exit in an awk script: all of them can finish before the producer does, and all of them turn the producer's death into the pipeline's verdict.
Three failures, three completely different causes, one sentence
This gate walks fourteen workspace views in a headless browser and blocks the deploy on any that fail. It had failed twice before this, both times reporting something about the application:
| run | what the gate printed | what was actually wrong |
|---|---|---|
| 2026-08-15 | 10 of 14 views failed, every one "no .fd-shell-layout" | Port 3000 was TriageDesk, started by another session. It vendors the same shell, so 4 views passed against a stranger's page |
| 2026-08-16, first | "never identified as FetchDue" | The probe itself triggers a cold next dev compile, and its 10s timeout was shorter than that compile. The dev log showed GET / 200 up to the second the gate gave up |
| 2026-08-16, second | "it answered but the body does not say FetchDue" | The body did say it. pipefail reported curl's broken pipe instead of grep's match |
The 08-15 run is the one that should worry you most. Four views passed on the wrong application, so the gate could have greenlit a deploy on somebody else's measurements just as easily as it blocked ours.
What changed after the second run was the failure message. "Never identified" was covering two states that need different responses, so the failure branch now runs one more probe and prints the HTTP code, the byte count, and the page's <title>, then splits on whether the code is 000:
if [ "$probe_code" = "000" ]; then
echo " it never answered ... the dev compile is still running or the server died." >&2
else
echo " it answered but the body does not say FetchDue. Title:" >&2
grep -o '<title>[^<]*</title>' /tmp/fd-gate-probe.html | head -1 >&2
fiThat diagnosis block is what printed the title next to the complaint, which is how the pipefail bug got found the same morning it was written.
That's how we built FetchDue.
One shipped product, taken apart, once a month. What it does, what it cost to build, what the pipeline behind it looks like, and what the numbers did — read off the repository and the live site, not written from memory. Join the list.
← All writing