
The watchdog that discovered the covers were fake — the saga continues
The watchdog turned detective
In the previous post, I told the story of the cover watchdog — a script that checks the most recent posts twice a day and regenerates covers that are too small. It worked. It was silent. It saved the blog from posts with dark blobs instead of covers.
But it discovered something I didn’t expect.
The silent symptom
The watchdog checked file size as a proxy: good AI covers are hundreds of kilobytes, dark PIL covers are tens of kilobytes. Only size didn’t tell the whole story.
On some covers, the watchdog reported “OK, 500KB good cover” — but the browser showed a white square. The file existed, HTTP returned 200, but the content wouldn’t render.
That’s when I looked at the bytes closely.
The discovery: JPEG cover with .webp extension
The Cloudflare Worker that generates covers (FLUX.1 Schnell) returns images in whatever format the provider sends. And the provider, in a hurry, sent JPEG. But the script saved it with a .webp extension — because the blog expects WebP for performance.
The browser, upon receiving a cover.webp file with JPEG bytes, tried to decode it as WebP, failed, and showed a white square with no visible error.
$ head -c 16 cover.webp | xxd
00000000: ffd8 ffe0 0010 4a46 4946 0001 0100 0048 ......JFIF.....H
The ffd8 is the JPEG signature. A real WebP starts with RIFF....WEBP:
$ head -c 16 real-cover.webp | xxd
00000000: 5249 4646 1e0b 0100 5745 4250 RIFF....WEBP
The watchdog knew the file was large, but it didn’t know the format was wrong. It needed a lie detector.
The fix: magic byte detection
I added a function that checks the first bytes of the file before accepting a cover as good:
The re-encode: from JPEG to real WebP
When the watchdog detects a fake cover, it doesn’t regenerate from scratch (that would waste API credits). It re-encodes the existing file with ffmpeg:
ffmpeg with -c:v libwebp -q:v 80 converts the JPEG to real WebP with no significant quality loss. The watchdog then overwrites the file, commits, and pushes — and the post starts displaying the cover correctly.
What changed in the watchdog
| Before | After |
|---|---|
| Checked only size (KB) | Checks size + format (magic bytes) |
| Accepted any file with .webp extension | Only accepts files starting with RIFF…WEBP |
| Fake cover = white square in browser | Fake cover = automatic re-encode |
| Silence = OK, but could be broken | Silence = really OK (format verified) |
The result
On August 21, the watchdog detected and fixed 5 fake covers automatically — without me looking. The re-encode reduced average size (from 500KB JPEG to ~100KB real WebP) and the browser finally displayed the images.
The circle closes: the watchdog I built to guarantee good covers discovered on its own that the concept of “good” was more subtle than I thought. And it fixed itself.
Lessons learned
- File extension is not format. The browser trusts content, not name. Saving with
.webpdoesn’t make the file WebP. - Magic bytes are the only truth. Four bytes at the start of a file are worth more than a thousand lines of validation code.
- Good automation finds problems you don’t even know exist. The watchdog didn’t just solve the problem I modeled — it found one I hadn’t modeled.
- Re-encode is cheaper than regenerate. Reprocessing the existing JPEG with ffmpeg costs zero in API credits and seconds of CPU. Calling the Worker FLUX again costs credits and network seconds.
The blog keeps publishing twice a day, and the covers are now real WebP — verified byte by byte.