Vibe Coders Have a Reading Problem
Writing code has never been cheaper. Reading it has never been more expensive. Most of us haven't repriced.
The page loaded. That should have been the end of it.
I’d just shipped a feature in the Next.js agent evaluation suite I’ve been building. The button did the thing the button was supposed to do. A spinner spun. A row appeared in the database. By every measure I’d asked for, the work was done.
I opened DevTools anyway, the way a mechanic listens to an engine after a repair, and clicked the button once.
The endpoint fired four times.
I want to describe the feeling, because I think the feeling is the whole point. It wasn’t alarm. It wasn’t even surprise. It was the small, almost pleasant click of a suspicion being confirmed before you knew you were suspicious. Four requests, stacked neatly in the panel, identical payloads, identical responses, all triggered by one human gesture.
The feature worked. The feature was also, in some quiet, expensive way, broken.
Nothing in the world had been going to tell me that. Claude Code had done exactly what I asked and nothing more — it even told me I was absolutely right many times. The page, dutiful and unbothered, had loaded.
This is what I mean when I say vibe coders have a reading problem. Not literacy in the schoolyard sense. Reading as in: looking at a thing that appears to be working and asking it to prove that it is. Reading as in the network tab, the rendered DOM, the actual query Drizzle produced, the diff you didn’t ask for.
Coding assistants optimize for what you ask it to optimize for. They don’t optimize for what you don’t think to measure.
And measuring is reading, and reading is slow, and slowness, right now, feels like the one luxury we’ve all agreed we no longer have.
The Network tab
The bug, when I tracked it down, was the kind of thing that gets flagged in a careful code review but that no model I’ve used has flagged unprompted: a useEffect watching a config object recreated inline on every render. New object identity, new effect run, new fetch, new state, new render, new object. A re-render storm.
The code looked fine. It was wrong in the specific way that only running code, watched closely, makes obvious — the Network tab was a waterfall the moment the page loaded.
I fixed it in two lines — wrapping the object in a useMemo with an honest dependency array. The fix took eleven minutes. Finding it took instinct I’m not sure I’d have if I’d grown up prompting instead of debugging.
I’ve started doing something else, too, and I want to be careful here, because it sounds like a productivity tip and I don’t mean it as one.
Hats
“You’re totally right!”, until you’re not
Every couple of features — every time the codebase crosses some invisible threshold of accumulation — I stop adding things and ask the agent to take off the implementer hat and put on an adversary’s. Architect’s hat. Performance reviewer’s hat. Security auditor’s hat. Read this codebase like you’re being paid to find what’s wrong with it.
The default mode of these tools is yes-and — extend, comply, ship. It’s a beautiful disposition for getting things done and a slightly dangerous one for noticing that the things you’re doing have started to rhyme with a mistake.
The first time I ran one of these passes, the reply came back as a tidy list of things I had been ignoring for a week. Not bugs, exactly. Drifts. An import that had reached across a layer it shouldn’t. A handler that caught an error and swallowed it. A type that had been quietly load-bearing in a way no one signed off on.
I read it twice. The feeling wasn’t quite embarrassment, though there was some of that; it was closer to the warmth of being shown the room you’ve been living in with the lights finally on. I’d seen all of it. I had not been looking.
The boring refactor that pays you three times
What surfaces on these passes is rarely dramatic. A 600-line component that wants to be five smaller ones. A prop drilled through four layers when context would have done. I split things up. I rename. I move a file.
The codebase gets a little more legible to me, and here is the part I didn’t expect: it gets a lot more legible to the coding assistant, too. Smaller files mean less unrelated context loaded per task, sharper boundaries, fewer wrong turns on the next request — Claude Code burns fewer tokens and makes fewer mistakes against a well-modularized repo.
A 600-line component is a kind of tax. You pay it every time the agent loads it into context, every time the runtime re-renders it, every time the bundler ships it.
But that’s only half of it, and the other half is the part I keep not seeing written down. A 600-line component is a kind of tax. You pay it every time the agent loads it into context, every time the runtime re-renders it, every time the bundler ships it.
In Next.js, the same boundaries that shrink your prompt context shrink your client bundles. A 600-line client component drags every one of its imports across the server/client boundary with it; split into focused pieces, most of them turn out not to need to be client components at all. The parts that genuinely need interactivity stay on the client. The rest move to the server, where they cost zero kilobytes to ship. Route-level code splitting suddenly has something to split. The bundle shrinks. The page gets faster.
None of this required “doing performance work” - it required reading the codebase honestly. Architecture, which I used to think of as a gift to my future human self, has quietly become part of the prompt, part of the render, and part of the page weight. Same discipline, three bills it pays.
The other thing that happens, on these review passes, is that I learn. I learn a TypeScript pattern I’d been working around for weeks. I learn why my generic was inferring unknown and what to do about it.
I have, in the last year, picked up more idiomatic TS from these conversations than from any book I’ve half-finished. Which, given that I’m coming to TypeScript from a long stretch of Python, is not a low bar.
The learning happens almost exclusively when I cast the model as a teacher and not a hand. It is the best tutor I’ve ever had. It is also, in implementer mode, perfectly content to let me stay exactly as ignorant as I was when I started.
The question
Which brings me to the third thing, and the one I find hardest, because it requires me to be smaller in the conversation than I instinctively want to be.
I’ve been trying to stop instructing. I’ve been trying to ask.
“Implement X this way” is a sentence I used to type a hundred times a day. It feels efficient. It feels like leadership. What it actually does is cap the ceiling of the output at the ceiling of my own knowledge on a Tuesday afternoon.
“I’m thinking of doing X — what are your thoughts?” costs the same tokens. Sometimes fewer. The reply is different in a way I find hard to describe except to say there is room in it.
Room for the model to mention the thing I didn’t know to ask about. Room for a tradeoff I hadn’t considered. Room, occasionally, for a flat “I wouldn’t do that, and here’s why,” which is the sentence I most need and most rarely get when I’ve already announced my answer in the question.
The problem is not you, Claude Code. The problem is me.
The conventional worry about AI and engineers is that we’ll get lazy. I don’t think that’s quite right. Lazy people, in my experience, know they’re being lazy.
The thing I’m watching happen, in myself on bad days and in others on most days, is subtler and stranger. It is the feeling of productivity uncoupled from the practice of attention. It is shipping without reading. It is the page loading and the laptop closing and the network tab, unwatched, firing four times into a void no one has agreed to look at.
Parting words
I should say this plainly. I am writing from inside this, not from some clean perch above it.
I have shipped code I didn’t fully read. I have accepted diffs because the tests were green and the hour was late and the difference between green and understood felt, at the time, like a difference I could afford to defer.
What I’m trying to hold onto is reading. Not as discipline — as the kind of curiosity that makes a person open a panel they didn’t have to open, on a feature that already worked.
That curiosity is, increasingly, the part of the job no one can do for me. It might be the part that’s still mine.




