Aug 20, 2026

What rots quietly

./blog.sh doctor asks whether the installation is sound and takes a second. It has nothing to say about an archive of four thousand posts in which one picture never downloaded, three permalinks lead nowhere, and two posts claim the same old address.

./blog.sh check walks all of it and says.

What it looks for

Each finding names a post and a slug — something to go and fix — and carries a line saying what to do about it.

  • Media a post asks for and hasn't got, a video's poster included. Usually an import whose download failed. The page renders a hole.
  • Images stored as 1px or smaller. The build treats those as tracking pixels and drops them together with their caption, so the page loses both without saying so. That one had been happening for a while before anything looked for it.
  • Internal links pointing at nothing — typically a permalink left over from an import.
  • Media directories no post owns, left by a deleted post or an import that ran twice. They cost disk, not correctness, so they are warnings.
  • Files in a post's own directory that the post no longer names. An import only ever adds, so a source that drops a picture between runs leaves its file stranded.
  • Two series whose names differ by a character or two — one series with a typo that quietly founded its own. Names differing only in digits are left alone: rok-2025 beside rok-2026 is two year-series, not a mistake.
  • One old address claimed by two posts. Whichever renders last wins and the other's readers land on it.

What it said about this site

Checking this archive

Reading posts: 37/37

⚠️  Post fourteen-years-every-image-came-home no longer names the file
   01.jpg in its media directory.
   Left behind by a picture the source dropped or renamed between
   imports. The post does not reference it; delete it yourself once you
   have looked.

0 problem(s), 1 thing(s) worth a look.

One warning on a small, carefully kept archive, and it is the finding this release added. The fix line guesses at an import, because that is the usual culprit — but nothing on this site was ever imported. That stray file was mine, and its story is duller and more ordinary than any import. I wrote that post one morning; forty minutes later I added a screenshot to the front of it, everything below shifted one place down, and the picture that had been 01.jpg was written again as 02.jpg. Same bytes, same checksum, new name. The post went out that evening carrying six pictures, and the seventh — the old name of one of them — sat in its directory for the next two weeks, 124 kB that nothing pointed at.

Which is the better argument for the check than the one I had. It doesn't care who left the file, and here it caught a case where nothing was lost and nothing was broken — just a copy of a picture the post still has, under a name it no longer uses.

The file is gone now, and running it again says so:

✅ Nothing wrong inside the archive: 43 post(s), with media, links and
   redirects all checking out.

The archive is sound.

It only reports

Nothing here deletes a directory or rewrites a post. The value of a checker is that its output can be trusted, and a checker that also acts has to be trusted twice.

It exits non-zero on errors alone, never on warnings, so it can hang off cron and speak up only when something is actually broken. Long lists are capped at twenty lines per kind — but the counts in the summary are the archive's, not the screen's, so one bad import cannot bury everything else and cannot flatter the totals either.

--online is a different tool wearing the same name

It asks the web about every link that leaves the site, and it takes minutes rather than a second, which is why you have to ask for it by name.

What it is willing to call dead is deliberately narrow: a host that no longer resolves, and a page answering 404 or 410. A timeout, a refused connection, a 5xx or a 403 is the web saying not right now — reporting those would turn one flaky evening into forty findings that are all fine tomorrow. Anything that looks dead is confirmed with a second request before it is believed, because some servers answer a HEAD with 404 and a GET with 200 for the same address.

Verdicts are remembered for a fortnight, so next week's run only asks about links it hasn't seen lately.

The hardest part was not finding things

It was not finding things that aren't there. The first honest runs called a healthy archive broken: every internal link into a listing page, a tag page or a series page came back dead, because those addresses are written by the build and check reads the content. A checker that exits non-zero every night on a sound archive trains you to ignore the night it means it.

One deliberate exception survives that pass. A link to a series with only one published part is still reported, because a series listing exists from the second part on — the address really is empty today, and it starts working the moment you publish part two.

Comments