Back to Blog
Engineering · Essay

The green check is a claim, not a result

Simon Doba·August 17, 2026·8 min read

There is an argument going around that AI coding costs us something the syntax never did: the mental model. That being stuck on a bug for two days was compression rather than waste: a little more of the system fitted in your head afterwards. Replace “hypothesise, inspect, understand, change” with “describe, generate, run, ask it to fix” and the model never gets built.

I think the argument is right and the reason is narrower than it sounds. It is not nostalgia for assembly. It is that a mental model is what lets you disbelieve a green check.

Three things that reported success last week

All three are from this site’s repository, in one week, and all three were found by a person rather than by a check.

A parser that read zero tokens and called it a working request. Hetzner’s inference API sends server-sent events as data:{…} with no space after the colon. My parser split on the documented data: prefix, matched nothing, and returned a successful response with empty output. For a few minutes I believed the models were broken. Nothing was thrown, no status was wrong, no test failed. The only thing that caught it was knowing roughly what a streamed completion should look like and noticing that this one looked like nothing at all.

A middleware that compiled and never ran. Next.js 16 renames middleware to proxy. I renamed the file in place and the build passed. The route that answers 410 for retired URLs started answering 404, silently, because the convention wants the file beside app/ and mine sat at the repository root. The build cannot tell you this. The manifest cannot either — it is empty whether proxy works or not. What told me was curling a URL whose correct answer I already knew.

A file that said it was generated and was not. The data module behind a chart carried a header reading “Generated by scripts/bench-inference.mjs”. It was not: the script wrote JSON, and I had produced the TypeScript by hand once and moved on. Every number in it happened to be right. Nothing would have noticed when they stopped being right.

What the three have in common

Not one of them is a bug in the sense that a type checker, a linter or a test recognises. Each is a gap between what a system reported and what it did, and each was closed by holding a model of the system next to its output and finding the two did not match.

That is the skill. It is not writing the parser; that is fifteen lines and a model wrote most of it. It is knowing that a working stream produces hundreds of small frames, so zero is not a small number but an impossible one.

Abstraction was never the problem

Programming has always been layers. You write TypeScript, which becomes JavaScript, which V8 parses to bytecode, profiles, and JIT-compiles into machine code for an instruction set, while an operating system mediates memory and scheduling, above caches and branch predictors and gates. Nobody mourns hand-written assembly, and the abstraction is why systems of this size exist at all.

What is different now is not the height of the stack. It is that the new layer answers back. A compiler that cannot produce your program says so. A model that cannot produce your program produces something else without hesitating, and the failure arrives as output rather than as an error.

So the loop is not degraded by having a machine in it. It is degraded when the machine’s report becomes the last word. “It compiled” is a claim. “The test passed” is a claim about the test. A review agent once filed three findings against a post of mine and one of them was invented. It was the most quotable of the three, which is why it was the hardest to reject.

What I am keeping

  • A prediction before the run. Not a guess at the output, a guess at its shape. Hundreds of frames. A 410, not a 404. Roughly forty tokens a second. Being wrong about the shape is the signal; being right is free.
  • One check the tool cannot make. Curl the URL. Read the built HTML. Open the page. The tool reporting on itself is the claim, not the evidence.
  • Writing down why, not what. A comment saying what the code does is redundant with the code. A comment saying which wrong version was tried first is the mental model in durable form, readable by whoever inherits it, models included.

None of that requires writing less with AI. I write far more with it than without. It requires keeping the part of the loop that was never about typing.

Next

Three failures in one week is a pattern I noticed, not a rate I measured. The version worth having: log every time a tool reports success and is wrong, for a month, with what caught it. If the catcher is usually a prediction made beforehand, that is a finding. If it is usually luck, that is a more interesting one.

What caught the last silent failure you had: a test, a user, or the fact that the output looked wrong to you? I suspect the third is more common than we admit, and it is the one nothing in the toolchain replaces.

The three failures are from this site’s repository between 10 and 12 August 2026, each written up in its own pull request.

Share this article

Building something similar?

I write about setups I actually use. If you're working on something comparable, I'd be curious what your workflow looks like.

Get in touch

Cookie Settings

We use cookies for analytics and to improve our website. Privacy policy