Skip to main content

Four bugs that cost me a day each — and every one of them was my tools lying

A crawler that invented failures, screenshots that were black, middleware shadowed by an empty folder, and a render pipeline with no renderer. Four days lost, one habit that would have saved them.

The bugs that cost me the most time have never been hard bugs. Hard bugs are honest — they announce themselves, they produce a stack trace, and you fix them. The expensive ones are the bugs where a tool lies to you confidently and you spend the day debugging the wrong thing.

Here are four from the last few months, each of which ate roughly a day. They're unrelated in subject and identical in shape.

1. The crawler that invented failures

I run a site crawler as part of checking my own projects: broken links, redirect chains, missing headers, that sort of thing. On one site it came back with a wall of red — security headers missing, structural problems, scores dragged down across the board.

I spent hours checking headers that were, demonstrably, being sent. curl showed them. The browser showed them. The crawler insisted they weren't there.

The actual cause: the crawler was requesting pages with Brotli compression accepted, and it couldn't correctly decode the Brotli responses. Everything it "read" from those pages was garbage, so everything it checked failed. The scores were understated across every single page.

Forcing gzip fixed it instantly.

What I learned: when a tool reports a problem, verify the problem exists using a different tool before you start fixing. One curl -I at the start would have saved the day, because it would have shown the mismatch immediately.

2. The screenshots that were black

I was building a game in Unity and wanted the build to photograph itself as part of an automated check — take a screenshot, look at it, confirm the scene is actually rendering.

Two traps stacked on top of each other. First, rendering the camera to a texture captured the 3D scene but silently omitted the on-screen debug UI drawn in immediate mode — so the exact information I wanted to verify was never in the image. Then, when I switched to grabbing the screen at the OS level, macOS returned a perfectly valid, perfectly black PNG. No error. No permission prompt. Just black.

A green tick on a pipeline that was photographing nothing.

What I learned: make the thing under test produce the evidence itself, from inside its own render loop, and make it fail loudly when it can't. An automated check that can't distinguish "everything is fine" from "I saw nothing" is worse than no check at all, because it manufactures confidence.

3. The middleware that worked in production and not locally

A Next.js site had middleware handling redirects. In production it worked. Locally it did nothing at all — no logs, no effect, as if the file didn't exist.

I checked the matcher config, the file location, the runtime, the Next version. All fine.

The cause was an empty src/pages directory left behind by an old migration. Its mere existence changed how the project's structure was resolved, and the middleware at the root was shadowed out of the build. Deleting an empty folder fixed it.

The tell was in the build manifest — the middleware simply wasn't listed. I found that after hours of reasoning about my code, when I could have found it in one minute by asking what the framework thought it had built.

What I learned: when behaviour differs between two environments running the same code, stop reading the code. Compare the two build outputs. The difference is in what got built, not in what you wrote.

4. The renderer that had no renderer

Automating Blender headlessly to produce video, I had a pipeline that ran end to end without errors and produced no video file. Two separate reasons, discovered in sequence: the build I was driving had no FFmpeg writer available at all, and an API I'd relied on for animation curves had been removed in the version I'd upgraded to. Then, once frames did come out, the default colour management made flat cartoon colours look washed-out grey — which isn't a bug at all, just a default doing exactly what it's designed to do, in a context where it's wrong.

What I learned: with any headless tool, verify the capability exists before building the workflow that depends on it. One line that asks "can you write this format?" up front, rather than inferring it from the absence of a file at the end.

The shape they share

All four have the same structure:

  1. A tool reported something — a failure, a success, an empty result.
  2. I trusted the report.
  3. The report was an artefact of the tool, not a fact about my system.

The fix isn't to distrust everything. It's cheaper than that: before acting on a report, confirm the same fact from one independent direction. A second tool, a raw request, the build output, a direct capability query. It costs a minute and it either confirms your reality or exposes the lie immediately.

The second habit is stricter: treat a silent success as a failure until proven otherwise. The black screenshot passed. The empty video pipeline exited cleanly. The middleware failed by doing nothing. In each case the system's way of telling me something was wrong was to say nothing at all, and I only lost the day because I read silence as consent.

None of this is sophisticated. It's just the difference between debugging your code and debugging your instruments — and knowing which one you're actually doing.