There is an old assumption buried in every piece of advice about reading code: that somebody, somewhere, understood it fully once. The author knew what every line did. Reading was archaeology — recovering an understanding that had already existed.
That assumption has quietly stopped being true. When an agent writes four hundred lines of a document-processing pipeline in ninety seconds, nobody has ever held the whole thing in their head. Not the agent, which has no head to hold it in, and not you. The code exists, it runs, and its complete meaning has never been present in a single mind at a single moment.
This is not a disaster. It is the ordinary condition of anyone who commissions work rather than performing it. A magazine editor does not write the pieces. A film producer cannot light a scene. What they have instead is a specific and learnable skill: knowing which parts of the work they must personally verify, and which parts they can accept on trust without being reckless. That skill is judgement under partial understanding, and it is now the central competence of shipping software you did not write.
The impossible standard, and why dropping it helps
The advice you will hear is “never merge code you don’t understand”. It sounds responsible. It is also unfollowable, and the attempt to follow it makes you worse at the job.
Try it honestly. An agent adds a survey-export endpoint. To understand it fully you would need to know what the ORM emits as SQL, how the connection pool behaves under load, what the web framework does with an exception raised mid-response, how a cloud host’s idle timeout interacts with a long query, and how the JSON serialiser handles a Decimal. Nobody knows all of that. Engineers with twenty years of experience do not know all of that. They ship anyway, because they have replaced “understand everything” with something better: a map of what matters.
Holding the impossible standard has a predictable failure mode. You read every diff with the same flat, dutiful attention, run out of energy around the fourth file, and start skimming — and the skimming is undirected, so it is as likely to skip the money-moving code as the log formatting. Uniform attention is the same as random attention. The goal is deliberately uneven attention.
What you are allowed not to know
Some things are genuinely safe to treat as opaque. They share a property: if they are wrong, the wrongness announces itself immediately and loudly, in front of you rather than in front of a client at 3am.
- Anything the type system or a validator will catch. If a Pydantic model says a field is an
intand the agent passes a string, you find out the first time you call it. - Anything a test exercises end to end. Not “there is a test file” — a test that calls the thing and asserts on real output.
- Pure formatting and presentation. A misaligned dashboard column is embarrassing. It is not an incident.
- Idiomatic boilerplate you have seen a hundred times. A standard router declaration, a standard React component shell. Read it for shape, not for meaning.
And some things are never safe to skim, no matter how tired you are or how confident the agent sounded. The dividing line is not difficulty. It is consequence.
Blast radius is the question
Before reading a diff line by line, ask one thing about every changed file: what is the worst thing this code can do if it is wrong? Answer it in four buckets, roughly in order of how much sleep you should lose.
| Blast radius | Looks like | Attention it earns |
|---|---|---|
| Destroys data | DELETE, an UPDATE without a narrow WHERE, migrations, deleting from an R2 bucket, truncating a D1 table, overwriting a file | Read every line. Twice. Check the filter. |
| Spends money | Loops that call an LLM, retries without a cap, per-row API calls, unbounded fan-out | Find the bound. If there is no bound, that is the bug. |
| Leaks data | Auth checks, query filters scoped by user or client, logging, error responses, CORS, anything touching respondent data | Read every line, and ask “whose data can this return?” |
| Merely looks wrong | Copy, layout, chart colours, sort order in a dashboard | Glance. Ship. Fix on report. |
The value of this is that it tells you where to stop. An agent refactors a settings page and touches eleven files. Ten of them are display code. One contains the line that decides which client’s responses a query returns. You have a limited budget of real attention; spend nearly all of it on file eleven.
Two habits make this sharper. First, learn the vocabulary of destruction in your own stack, so it jumps out visually: delete, drop, truncate, overwrite, force, a wildcard in a path, while True. Second, be suspicious of anything with no natural ceiling. Production incidents are rarely caused by a wrong calculation. They are caused by a correct calculation performed 400,000 times.
”I read it” versus “I know what happens at 3am”
There are two very different states that both get described as having reviewed the code.
The first is comprehension of the happy path: you traced what the function does when the inputs are sensible, the network is up, the database answers, and nobody is doing anything strange. This is what almost everybody means by “I read it”, and it is the state an agent’s own explanation will helpfully put you in.
The second is harder and more valuable. It is knowing how the code behaves when things are bad. There is a short list of questions that gets you there, and it barely changes between projects:
- What happens if this is called twice — a user double-clicks, a queue redelivers, a cron job overlaps with its own previous run? Does it do the work twice? Does that matter? Charging a card twice matters. Recomputing a cached figure twice does not.
- What happens if the thing it calls is slow rather than broken? Slow is worse than broken. A failure returns and frees the worker. A ninety-second hang holds a connection, then another, until the whole service stops answering.
- What happens if it fails halfway? Three of five writes have landed, and the fourth raises. Is that state coherent, or have you invented a survey response with no respondent?
- Who gets told? If this quietly returns zero rows forever, does anything anywhere notice? Silent partial failure is the most expensive bug class in automation, because it looks exactly like success.
You will not answer these for every diff. Answer them for anything in the top three rows of the blast-radius table, and you will catch most of what actually wakes you up.
Read the seams, not the middle
Bugs are not evenly distributed. They cluster where one system hands something to another, because that is where two sets of assumptions meet and nobody owns the contract between them.
The middle of a function is usually fine. An agent is genuinely good at writing a loop that transforms a list. What it is less reliable at is knowing what the other side expects — because the other side is in a different file, a different service, or a different company’s documentation.
So the seams are worth your eyes:
- Where untrusted input becomes trusted data: the validation boundary.
- Where your code calls something over a network: another API, an LLM, a database on another continent.
- Where data crosses a type boundary: JSON to Python, Python to SQL, SQL to JSON again. Dates, decimals, nulls and empty strings get mangled at exactly these points.
- Where work changes hands: a request handing off to a background task, a queue, a scheduled job.
- Where a failure changes category: a caught exception becoming a return value, a timeout becoming an empty list.
Here is a seam bug in three lines, of the kind agents emit constantly:
# No timeout argument means no ceiling on waiting — this can hang until the worker dies.
r = httpx.get(f"{SURVEY_API}/responses", params={"since": since})
return r.json()["items"] # KeyError the first time the API returns an error body
Nothing in the middle is wrong. The loop that would process those items is probably perfect. Both bugs are at the boundary: no ceiling on waiting, and an assumption about a response shape the other side never promised. The fix is unglamorous and lives entirely at the seam:
r = httpx.get(f"{SURVEY_API}/responses", params={"since": since}, timeout=10.0)
r.raise_for_status() # turn a 4xx/5xx into an exception here, not a KeyError later
return r.json().get("items", [])
Make the agent explain its own diff
The single highest-return review habit is asking the agent to explain what it wrote — but the value is not in the explanation. It is in the comparison. You now have two accounts of the same change: the code, and a claim about the code. Discrepancies between them are where the bugs live.
Ask in a way that is hard to answer with fluent nothing. A weak prompt is “explain this code”. Stronger ones:
- “Which line decides which client’s data this returns?”
- “What happens if this runs twice at the same time?”
- “Which of these changes could delete or overwrite existing rows?”
- “What did you assume about the survey API’s response that its documentation doesn’t guarantee?”
- “If this fails in production, what will the error message actually say?”
Then judge the answer. A good explanation names specific lines and specific values, says where the risk is concentrated, volunteers what it is unsure about, and describes failure in concrete terms: “if the upstream returns a 500, line 14 raises KeyError rather than a clear error, because the error body has no items key.”
A fluent explanation restates the code in prose and adds warmth. “This endpoint efficiently retrieves the survey responses and handles errors gracefully, ensuring robust operation.” Every word is plausible. Nothing in it is checkable. Treat “robust”, “gracefully”, “efficiently” and “properly handles” as blanks where a fact should be, and ask the follow-up that fills them: handles how, and what does it do on a 500?
Two further tells. An explanation that never mentions a downside is not a review, it is a sales pitch — ask directly what the riskiest line is, and be unsatisfied with “nothing”. And an explanation that disagrees with the diff, even trivially — it mentions a retry that is not there, or a filter you cannot find — is a gift. That gap is the most reliable bug detector you have, and you did not need to understand the whole file to find it.
The editor’s real job
None of this makes you the author. You will merge code whose every line you could not reproduce, and that is fine; it was always going to be true, and it is true of every engineer working in a large codebase.
What you owe the system is narrower and more achievable. Know where the sharp edges are. Spend your attention in proportion to consequence, not in proportion to line count. Read the joints rather than the middle. Keep asking what happens when it runs twice, hangs, or half-succeeds. And treat every confident explanation as a claim to be checked against the diff rather than a reason to stop looking.
Authorship was never the skill that kept production up. Judgement was. That part has not been automated.