Dzhuneyt

  Email Me  Book a meeting
"The AI wrote it" is not a defence

"The AI wrote it" is not a defence

Dzhuneyt Ahmed - Author of this post

By: Dzhuneyt Ahmed

Posted · 5 min read

You have reviewed this pull request. Forty lines, tests green, style indistinguishable from the rest of the file. Then somebody asks why one particular guard clause is there, and nobody can answer — not the reviewer, which is normal, but not the author either.

Provenance is a dead end

The instinct is to ask who wrote it. Resist it, because you can't find out. The description is generated. The style matches the codebase because the model was shown the codebase. Ask directly and you get a yes: they typed the prompt and read the output.

Effort was never a good proxy for understanding anyway. A diff could always have been three Stack Overflow answers stitched together, and it looked no different from work someone had thought about.

Two things did genuinely change.

The mistakes repeat. A human gets one thing wrong in one place. A model gets the same thing wrong in eleven places, confidently. Reviewing a sample of a diff assumes mistakes are scattered, and these aren't.

The author who came with it. A diff used to arrive with a person attached, someone holding a mental model you could question. Now there's nobody there by default, and you can't ask the model that wrote it.

What the diff stopped carrying

Not the author's comprehension. That was never durable — nobody can explain line 40 of an eighteen-month-old file, including the person who wrote it. They run git blame, find themselves, and remember nothing.

What's missing is the record of the decision: the dead ends, the approach that looked obvious and wasn't, the alternative that was rejected and why. Generated code didn't invent that problem. It moved the day you find out from month eighteen to day one, while someone is still looking at the diff.

Cheap verification doesn't decide what's correct

The obvious objection is that generation and verification got cheap together. Differential-test three generated implementations, generate two hundred adversarial inputs, run mutation testing on every pull request — and the bottleneck solves itself.

That's right about checking and wrong about the conclusion, because something has to say what "correct" means. Generate two hundred tests against the wrong contract and you have automated confident wrongness — a green suite defending the misunderstanding. Cheap verification makes checking cheap. It does nothing about deciding what to check.

So the scarce human act is no longer reading the code. It's saying what the code is supposed to do, clearly enough that something else can check the answer.

What I actually check

A reviewer is never the first thing that runs the code. Typecheck, lint, build, schema validation and the dependency gate all fail before a human opens the tab. A reviewer who spends attention finding an invented method name has none left for the IAM policy three files down.

One exception needs human eyes, and it is the highest value-per-second check here: every added dependency. Models hallucinate package names, attackers register them and wait, and a one-line addition to package.json carries no diff weight. Does this package exist? Who publishes it? Was it in the lockfile before?

After that, triage. Most pull requests get a skim. The full treatment is reserved by blast radius:

  1. Anything at a trust boundary. Auth, IAM policies, anything meant to fail closed. Slow eyes every time, no exemption for small diffs.
  2. New dependencies. Per above.
  3. Failure behaviour. What happens when this call fails? What does the revert look like?
  4. The tests, on their own terms.

That budget doesn't come from dropping style review — linters took that a decade ago. It comes from deliberately not reading most diffs closely.

On the tests: I used to say stash the change and watch them fail, which doesn't work, because the tests are in the same diff and you get a compile error rather than a verdict. Empty out the implementation body and leave the signature alone — return a wrong constant, or throw. If the test still passes, the test is decoration. That's mutation testing by hand; Stryker, PIT and mutmut do it exhaustively in CI, which is where it belongs.

Ask the author — but not in a comment box

A fluent answer in a review thread is the same artifact as a fluent description, with a question mark in front of it. What survives is synchronous and predictive: not "why did you do it this way" but "what breaks if this retry count is zero?" Someone who drove the change answers immediately. Someone who approved it goes quiet, or answers too late and too well.

One condition: this applies to every author, including me. Apply it asymmetrically — seniors interrogating juniors while nobody asks the staff engineer — and it becomes a loyalty test aimed at whoever is most visibly using AI. That is how the practice gets quietly killed in six weeks.

Who owns it

Review capacity is fixed, so the only lever left is the author. They wrote down what the change is supposed to do, and what it must not do, before generating a line of it. They ran the code. They annotated the judgement calls in the diff. They don't open a pull request they can't defend line by line. I read less carefully than I used to, and I can only afford that because of it.

Which makes "if you approve it, you own it" the wrong rule. It sounds strict, but it lets the author generate while the reviewer carries the risk, and that is backwards.

Split it. The author owns the change — they're the one woken up at 3am. The reviewer owns having looked at the right things, and answers for skipping those, not for a bug they had no way to see.

None of this catches the change that is wrong in a way nobody thought to check. Machine gates only fail on what they were told to look for, and triage lets some bugs through deliberately. The split settles who answers for that. It doesn't stop it happening.

Which is the uncomfortable part: the gate has to stop being reading. Reading never scaled, and cheap generation only removed the excuse. What replaces it, in the order I reach for them:

  1. machine gates
  2. blast-radius controls
  3. written-down intent, agreed before the code exists
  4. targeted human reading at the boundaries

The first thing I'd give up is line-by-line reading of everything outside triage, on purpose rather than by drift.

None of that changes who answers for the result. The tools are allowed to write the code. They aren't allowed to hold the pager, and they can't be called as a witness — which is why, when it breaks, "the AI wrote it" is not a defence.

Related posts

Dzhuneyt Ahmed

Dzhuneyt

Helping teams build reliable cloud infrastructure — without the bloated bill.

Social

My Other Blogs

© 2026 Dzhuneyt Ahmed. All rights reserved.