field note № 13 · grade the failure

Amber is not red

The daily restore-readiness job went red. The restore audit itself had succeeded — committed, clean, ready. What ran it red was a one-off Cloudflare hiccup on the step that just publishes the pretty page about the audit. The alarm fired for the wrong reason, and every alarm that fires for the wrong reason spends down the meaning of the alarm.

The weakness

One job, two very different things happening inside it. First the load-bearing work: audit whether the fleet could actually be restored from cold — repos, secrets, cold-data snapshot, runbook — and commit the verdict. Then the cosmetic tail: re-render and wrangler pages deploy a private dashboard about that verdict to a Cloudflare Pages surface. The audit is the signal. The deploy is just how the signal gets a nice page.

On the 08:05 run the deploy step caught a transient Cloudflare 522"received a malformed response from the API", a CDN blip that cleared on its own minutes later. But the deploy was wired … || RC=$?, so a failure to publish the page flipped the whole job's exit code. The restore-readiness signal — the thing you actually check to know you can rebuild — went red over a rendering hiccup, while the audit underneath it sat green and committed.

Why a false red is worse than it looks

1 · It couples a cosmetic step to a load-bearing verdict. The audit succeeding and the page publishing are two independent facts. One || RC=$? fused them, so the weaker, flakier one could veto the stronger one.

2 · It cries wolf. A board that goes red on transient CDN weather trains you to glance at red and shrug. The day the audit actually can't restore, red will have already lost its meaning — the exact opposite of what a status board is for.

3 · It fails on the first try, not the third. The deploy is idempotent — re-pushing identical content is safe. There was no retry, so a blip that a single automatic re-attempt would have swallowed became a human-visible failure.

The wrong fix (the one that's always tempting)

The lazy fix is to swallow the deploy failure entirelydeploy || true — so the job never goes red on it again. That doesn't grade the failure, it deletes it: now a genuinely broken publish (bad project, revoked token, a surface that's been dark for a week) looks identical to a perfect run. You've traded a red that cries wolf for a green that lies. The other tempting fix — match the error signature, retry only on 522/5xx — adds a brittle string parser to chase a failure that costs twenty seconds once a day. Both miss the real shape: the problem isn't which failure, it's that there's only one grade of failure.

A red that fires on a cosmetic hiccup is a red you'll learn to ignore.

The reframe

The job has two outcomes worth telling apart, and it was only reporting one. Split the single red into a ladder: the load-bearing audit crashing is red; the audit succeeding but the page failing to publish after retries is amber — degraded, worth a glance, not an emergency; everything working is green. Then make the delivery survive the weather it runs in: retry the idempotent step before you ever grade it a failure.

The fix

Three small moves in one shell script, no new dependency:

Guardrail — amber must be fail-loud, not fail-silent

Grading a failure down is not the same as hiding it. A degraded deploy still writes a loud line to the log, still flips the deploy_ok:false metric, still paints the board amber. It cannot stamp green. The distinction being drawn is degraded-delivery vs dead-capability — not problem vs no-problem. An amber that could quietly become green would just be the || true lie wearing a nicer colour.

The proof

3 attempts
exponential backoff on the idempotent deploy — ~20s worst case before it's ever called a failure. The original 522 clears on retry two.
0 → red
a survived-retry publish blip now touches the exit code not at all. The audit's verdict is decoupled from the CDN's weather.
1 grade → 3
one undifferentiated red became green / amber / red. Red now means exactly one thing: the restore audit itself crashed.
0 files
changed outside the one job script. The hub already mapped a fresh yellow verdict to ⚠ degraded — the tier was waiting to be used.

Both paths were exercised before trusting the schedule: the amber path emits verdict:"yellow" with deploy_ok:"false" and exits 0; the green path emits verdict:"green" with deploy_ok:"true". The retry() helper was unit-checked in isolation — succeeds on the third attempt, returns the real exit code after exhausting all three — so the backoff can't silently loop or silently pass.

Grade your failures, or the alarm grades them all the same — and then it grades nothing at all.

The principle, generalised

A status signal is only worth checking if its states mean distinct things. The moment a cosmetic hiccup and a load-bearing failure share a colour, the colour stops carrying information — and the human on the other end learns to ignore it. The durable fix isn't to make the flaky step never fail (you can't) or to hide it when it does (you mustn't). It's to give failure a ladder: retry what's idempotent, decouple the cosmetic from the load-bearing, and reserve the loudest state for the one condition that actually warrants it.

  1. Separate the load-bearing output from its delivery. The audit is the signal; the published page is just its courier. A courier stumbling is not the signal failing.
  2. Retry the idempotent before you grade it. If re-running is safe, a transient blip should cost twenty automatic seconds, not a human's attention.
  3. Decouple the flaky from the exit code. Never let the weakest, most weather-exposed step veto the strongest one via a shared || RC=$?.
  4. Give failure more than one grade. Green / amber / red beats pass / fail whenever "degraded" and "dead" are genuinely different — which is almost always.
  5. Keep amber fail-loud. Grading down is not hiding. Degraded must still be visibly degraded and must never be able to stamp green.
  6. Reserve red for the one thing that means it. When red only ever means the load-bearing work crashed, red is worth reacting to again.

This is remove the coupling, not the component (№ 1) applied to a status signal — the fragile thing was never the deploy, it was the || RC=$? that coupled a cosmetic publish to a load-bearing verdict. It's the complement of silence is a failure mode (№ 8): that note built a degradation ladder so a dropped feature degraded gracefully instead of vanishing; this one builds a degradation ladder so a transient blip degrades to amber instead of screaming red. And it's the near-twin of alert on the change, not the state (№ 11): both are about an alarm that fires so indiscriminately you stop trusting it — there, by re-firing on unchanged state; here, by firing on the wrong severity. The job itself is the one from a snapshot is a restore plan (№ 6) — the daily restore-readiness audit, now with an alarm you can believe.

antifragile.gf.cx · field note № 13 · published 2026-07-31 · distilled from live gf.cx practice (restore-audit transient-failure hardening · gfcx_restore_audit_run.sh) · sibling to № 1 (remove the coupling), № 8 (silence is a failure mode), № 11 (alert on the change), № 6 (a snapshot is a restore plan) · imports assets.gf.cx favicon + card primitives