Kyber Cypher Plain English edition
Text size Back to the full site
A green indicator lamp whose wire is not connected to anything, standing for an alert that can only ever say everything is fine.

The most dangerous alert is the one that says everything is fine

Part 2 of 5 of the series When the computer lies to you. Start at part one.

What you will learn

  • How a job can report success every night while doing nothing at all
  • Why a red warning is a gift and a wrong green one is not
  • The second check that catches what the first one structurally cannot
What happened and why it matters

A tidying up job ran every night for days and reported success every single time. It was telling the truth. It was also doing absolutely nothing, while the thing it was supposed to be keeping under control quietly grew past the point where anything else could read it.

Here is the shape of it, and it is worth following closely, because your own setup almost certainly contains one of these.

The job had one simple rule: take anything older than a few days and move it into long term storage. That rule is correct. It exists so that something still in progress does not get filed away underneath you.

Then everything arrived on the same day

Every item was a few hours old. Not one of them was older than a few days, so not one of them qualified. The job looked at the pile, correctly worked out that nothing was eligible, correctly did nothing, and correctly recorded that it had finished without error.

Now run that forward. Tomorrow the pile is one day old. Still too new. The day after, two days. Still too new. Every night a clean success message about work that is not happening, and every night the problem is bigger.

Nothing in the system was broken. That is the entire horror of it.

Why this is worse than a failure

A red warning is a gift. It interrupts you, it points at something, and it is impossible to ignore.

A green report that is wrong gives you nothing at all, and it does it while actively reassuring you. You never go looking, because you were just told everything is fine.

A way to picture it

Think of the smoke alarm you have never tested. It chirps happily on its monthly self check. That self check confirms the battery still has power. It does not confirm the sensor can still detect smoke, because the only way to confirm that is to put smoke in front of it.

So for years you get a reassuring chirp that is technically honest and practically worthless.

The real cause was not the rule

The rule was good. The cause is that the job had exactly one way of deciding what to do, and therefore exactly one way to be wrong, and no way whatsoever of noticing.

Every check carries an assumption. This one assumed that a problem builds up slowly enough that age is a reasonable stand-in for size. Most days that holds. On the day it does not, the check is blind and cheerful.

So the fix is not to loosen the rule. Loosen it and you start filing away live work, which is the exact harm the rule was written to prevent. The fix is to add a second trigger that watches a completely different signal. Age was the first. Size is the obvious second, and size does not care what day anything arrived.

The part most people skip

The second trigger must be able to fail out loud. If it tries to bring things back under the limit and cannot, because everything left is protected by the first rule, it has to say so.

If it quietly gives up and exits successfully, you have just built a second thing that lies to you, and you will trust it more than the first one because it is newer.

A check that can only ever say ok is not a check. It is a decoration that happens to run on a schedule.

How to do it yourself

This applies to any job that runs on a schedule, in any programming language. The first two steps only look at things and change nothing.

1. Write down the assumption hiding in every healthy report

What to do: go through your scheduled jobs and, for each one, finish this sentence: "this reports success when ______".

What you are looking for: if that sentence is only about the job not crashing, it is not measuring the outcome you actually care about. The gap between those two things is exactly where this failure lives.

# the job says "ok" when: it ran without raising an error
# you actually care about: the pile got smaller
# the gap between those two lines is the bug

2. Measure the outcome, not the run

What this does: records the thing you actually care about before and after the job runs.

Why it settles the argument: if a run reports success without moving that number, you now have evidence instead of a feeling.

before=$(measure_the_thing)
run_the_job
after=$(measure_the_thing)
echo "job ok: $before -> $after"   # suddenly obvious

3. Add a trigger that watches something unrelated

What this does: sets up a second rule based on size rather than age.

Keep the first rule exactly as it is. If the first is based on time, make the second based on volume. Two rules that can fail the same way are still one rule.

HIGH   = 80    # above this, the size trigger fires
TARGET = 40    # bring it back down to roughly this
KEEP   = 6     # never touch the newest few, whatever happens

4. Let the safe rule run first, then top up

What this does: applies the original age rule, and only then lets the size rule take more if the result is still over the limit.

Why the order matters: the second trigger is a backstop, not a replacement. Running it first would let it do the harm the first rule exists to prevent.

selected = pick_by_age(items, days=3)
if size_after(selected) > HIGH:
    selected += pick_oldest_until(items, TARGET,
                                  protect_newest=KEEP)

5. Make it announce a shortfall instead of exiting green

What this does: if the trigger cannot reach its target because everything left is protected, it says so in plain words.

This is the step that matters most. Staying silent here recreates the original bug in a brand new place.

final = size_after(selected)
if final > TARGET:
    print(f"SIZE TRIGGER fired but could NOT reach target: "
          f"{final} still over {TARGET}. Everything remaining "
          f"is protected. NOT silently ignored.")

6. Protect the newest and the unfinished

What this does: makes the size rule skip the newest few items and anything still marked as in progress.

Why: a backstop that causes the very harm the first rule was written to prevent is not a backstop.

if index in newest_n or item.is_open:
    continue          # never size-trim these, ever

7. Test the failing path on purpose

Force the situation where the trigger cannot reach its target, and confirm you actually see the shortfall message. Then run it against normal input and confirm it does nothing and says nothing.

You have not verified a check until you have watched it fail.

8. Prefer a loud wrong answer to a quiet right one

Given a choice between a job that occasionally complains unnecessarily and one that occasionally succeeds at nothing, take the complainer. You can calm a noisy check. You cannot fix a check that never speaks.

Next in this series: the monitor that reported dozens of outages at a service that was completely fine.

This page in the original Kyber Cypher voice: The worst alert is a green one