Aug 6, 2026

The guard that switched itself off

This site's deploy has two guards. If a build suddenly has far fewer files than what's live, or far fewer bytes, the deploy stops. They exist because a broken build looks exactly like a deliberate one to rsync --delete, and a static site is only ever one confident sync away from being erased.

In 1.0 they could turn themselves off. Permanently. Silently. Here is how, because the shape of this mistake is more useful than the fix.

The reference was the wrong thing

The guards compared the new build against the deploy manifest — a record of what's on the target. Reasonable, until an upload fails. A failed upload leaves the manifest out of true, and a guard measuring against a record it knows is wrong would fire on every subsequent run.

So there was a marker: after a failed run, stand the guards down until a clean run comes along and restores the reference.

You can see it already. When the failure is permanent — a file the host keeps refusing, expired credentials, a target that no longer exists — no clean run ever comes. The marker never lifts. The guards are off, and nothing says so. A build collapsing from 7,500 files to a handful would have been mirrored faithfully, --prune included.

The fix was to stop measuring the target

They now compare the build against the last build that was accepted, recorded before the first byte moves. That number doesn't care whether the upload then succeeded, failed, or died halfway — so there's no longer anything to stand down, and no marker to get stuck.

The reference stopped being "what's out there" and became "what I last agreed to". Nothing else had to change.

Three more things came out of the same review

They also fired when they shouldn't. Twenty per cent of a 32-file build is six files, so publishing two posts at once could abort a deploy — inside a flow ./blog.sh runs for you, which has no way to pass --force. The percentages carry absolute floors now.

Bytes are guarded in both directions. The same file count with every page nearly empty used to be invisible. A byte drop stops the deploy; a byte increase only mentions itself, because attaching media is authoring, not a fault.

An empty build is refused outright. With an empty manifest, it used to sail through every check that existed.

Why write this up

Because "we found a bug" is worth less than "here is the reasoning that produced it". The bug wasn't sloppiness. It was a patch that solved the problem in front of it and created a worse one behind it, and it survived because the failure mode was silence.

The lesson I'm keeping: a safety mechanism that can be disabled by the thing it's protecting against isn't a safety mechanism. It's a suggestion.

Comments