sm. Simon Mullis
Essay · Vibe Coding

The Second Hard Part

Generating the code was the first hard part, and it's largely solved. The second hard part is everything after, and it did not disappear because the typing got cheap.

The most valuable software in a large company is often imagined by someone who cannot write it. They run the team the tool is for. They know exactly which three fields matter and which report has been quietly lying for a year, and for two decades their only move was to become a customer of the engineering department: write it up, hand it over, wait. Now they can just build it. They describe what they want to a model, in the language they actually use, and a working tool comes back the same afternoon. I have watched this produce good, useful things, tools that solved problems tickets had described fruitlessly for a year. The first hard part of software, the part that needed a scarce skill, has largely fallen away. That is worth being glad about, and I am.

There is a wrinkle even in the good news. The domain expert does not arrive with a specification, because that was never their language. They arrive the way a customer always did, with a solution already chosen: put a dropdown here with these three options. The request is aimed at a real problem, but it is not the problem, and often the right answer is a different shape entirely. The old chain, for all its slowness, had someone in it whose job was to hear “I want a dropdown” and ask “what are you actually trying to do”, and arrive somewhere better. A model does not do that. It is the most willing builder you have ever met. It will build precisely what you asked for, beautifully, and never once ask whether you asked for the right thing.

It is getting better at this by the fortnight, and that is worth conceding, because a fair amount of the case against vibe-coding is a photograph of a fast-moving thing, already ageing. But the improvement runs in one direction. The model is getting better at building X. It is not learning to ask whether you wanted X or Z, and no fluency in carrying out a request ever becomes a question about the request. Better execution is not judgement.

So the shortcut is real, and it is not free, and the reason it is not free is the whole subject here.

The slog moved, it did not shrink

Writing code was never most of software. It only felt like it, because it needed a scarce skill and scarce skills soak up all the attention. Think of a restaurant judged entirely by the twenty minutes at the stove: the sourcing, the prep, the cleaning, the licences, someone there at six on a dead Tuesday when nobody comes, all of it invisible because the cooking was the part that looked hard to do. The rest was always the larger part: review, tests, secrets handling, continuous integration, deploy, rollback, observability, versioning, access control, the pager. Generation touched almost none of it. The volume of work did not fall when the code got cheap. It moved.

That list is only the back half of what was skipped. There is a front half, just as untouched, because it happens before there is any code to generate. Someone has to work out what to build, which is not the same as being handed it. Someone has to turn a vague want into something that can be demoed and argued with, and to notice that the person asking for a dropdown has a problem a dropdown will not fix. That is the work that used to sit between the request and the keyboard, and a model does not do it, for the same reason a taxi cannot choose your destination: it starts once the question is settled, and settling the question was the job.

This is where the shortcut is most seductive, and it is worth being exact about why. When the person building is also the only one who will ever use the thing, that front half barely exists: you know exactly what you need because you need it, and the value lives in your own head. Software built that way, fast, solo, every role at once, is real and useful, and I have shipped a great deal of it. But it is the rare case, not the common one. Usually there is a gap, and the gap is the whole reason the apparatus exists: the person who knows what the tool should do and the person who knows how to build it are almost never the same human. A developer is seldom also a malware reverse-engineer, or a fighter pilot, or the owner of fifty restaurants who needs a new point-of-sale. The committee, the review, the week a decision takes, all the things that feel like pure overhead: not ceremony. They are a bridge, built plank by plank over that gap, so that something can be made for people who are not you. What vibe-coding changes is that the domain expert can now jump the gap alone. What it does not change is that the far side is still there, with all its hard-won lessons, now in the hands of someone who was never handed them.

It moved, most sharply, from production to verification. Producing a plausible thousand lines is now nearly free. Knowing whether those thousand lines do what you meant, and do not do three things you did not, costs exactly what it always did, and may cost more, because you never built the mental model incrementally by writing them yourself. You are handed a finished stranger and asked to vouch for it. That is a harder read than reviewing a colleague’s careful diff, and there is a great deal more of it.

There is a way to say this precisely. Trust is the amount you will accept without checking. Generating code at this speed turns that dial hard up: the volume you are accepting on faith expands far faster than your capacity to check it. The water is rising and the bucket is the same size. The second hard part is what you do about the surface you are now trusting unchecked.

None of this shrinks as the models improve. Everything in this section is indifferent to how good the generated code is, because none of it was ever about that. A flawless thousand lines still has a blast radius, still holds secrets or does not, still needs eyes that are not its author’s. The first hard part keeps getting easier. The second does not move.

Obligation scales with blast radius

Not all of that surface matters equally, and the instinct to treat it as if it does is where most of the pain comes from.

A rough tool you built for yourself, that nobody else runs, needs almost nothing. No review, no pipeline, no rollback plan. It should stay that way; making someone justify a script only they will ever use is how you teach them the whole apparatus is theatre. A few habits earn their place even here, but for the builder’s own sake and no one else’s: keeping the thing in version control, not pasting a live key into it, being able to get back to yesterday’s working version. I do these on throwaway scripts, and if I am honest it is because they make my own life easier, not because a policy asked. That is the test for what belongs at this level, whatever pays the person doing it back first. The obligations owed to other people begin the moment something else comes to depend on the thing. A shared endpoint, a script in someone else’s deploy, a demo a customer will build their own product on top of. A go-kart you welded together in the garage owes the world nothing; put a stranger in the seat and start charging for laps and you have quietly signed up for tested brakes, a seatbelt, and a much longer conversation with your insurer. Nothing about the go-kart changed. Who was sitting in it did. The trouble is that things cross that line quietly, a toy passed to a colleague, a proof-of-concept posted to a public repository, and nobody marks the crossing.

Which means the question that decides how much process a piece of code deserves is not “how good is it”. Quality and blast radius are different questions, and a system that gates on the first while ignoring the second gets both wrong: it holds up a careful little tool nobody depends on, and waves through a slapdash one that half the company is about to. A demo a prospect will treat as a starting point and a service that takes live traffic sit at opposite ends of that scale. They should not inherit the same floor. The obligation is set by what depends on the thing, and by nothing else.

Nobody is failing. The net was just invisible

Here is the part the crisis coverage gets wrong, and gets wrong in a way that makes everything harder.

The new builders experience the failures as traps laid for them personally. They leak a credential, or ship a dependency with a known hole, and they read it as proof that they do not belong here, that these landmines are the price of their newness. So any advice framed as their responsibility, install this hook, remember to check that, lands as confirmation: yes, this was your fault, and a real developer would not have done it. Or they finish something solid and expect it to simply go live, and are stopped by a security review they did not know existed, and are baffled by it, because the whole approving side of software, the part whose job is to say not yet, had never once been visible to them.

It is worth saying plainly, because it is true and because it dissolves the shame: leaking a credential is not a beginner’s mistake. It is one of the most common security incidents in professional software, committed constantly by engineers with twenty years behind them. In 2025 roughly twenty-nine million new secrets were committed to public repositories on GitHub alone, a third more than the year before and the largest jump ever recorded, and most of them were still valid years later (GitGuardian, 2026). This is not the failure of the inexperienced. It is the steady state of the whole field.

Experienced developers are not more careful. They are the trapeze act that stopped noticing the net years ago, a net strung up precisely because everyone kept falling: secret scanning wired into the platform, dependency checks on every push, an entire tooling industry that exists for no other reason. The veteran has long since stopped looking down at it. The newcomer looks down, sees nothing, and mistakes the missing net for a defect in themselves. The same numbers bear this out from the other side: commits made with an AI assistant leak secrets at more than double the rate of human-only ones, which is not a story about carelessness but about the dial turned up for everyone, veteran included. The net matters more now, not less.

And this takes the sting out completely, once it is seen. The check is not a response to suspicion. Nobody reviews your work because they doubt you in particular. Everything is checked, always, the newcomer’s and the thirty-year veteran’s and the flawless build that turns out to have nothing wrong with it at all, because the check is the floor and the floor has no exceptions. It is not “we doubt you.” It is “we doubt everything, equally, forever.” Read that way, being checked stops being a verdict on you and becomes something closer to the weather: constant, impersonal, and nothing to do with your worth.

One distinction has to stay visible, or the argument is too soft to trust. Everything above explains the ordinary incident, the mistake made by someone who did not know the trap existed, and that is nearly all of them. It does not explain the person who is shown the net, understands exactly what it is for, and steps outside it anyway because they have decided the bar does not apply to them. That is not a process failure. That is a choice, and it is held to firmly. Keeping the two apart is precisely what earns the generosity for the first case, where almost all the volume lives.

Gate on the objective, advise on the subjective

This is the half that matters most, and the half almost nobody gets right.

When a review goes wrong, the reviewer is usually holding two entirely different kinds of claim and delivering them as one. There are objective claims: this leaks a credential, the tests fail, this dependency has a known vulnerability, there is no way to roll it back, it writes to production with no dry run. These are checkable. Two competent people will agree on them. No taste is involved. And there are subjective ones: the structure is odd, I would have used a different pattern, the naming is not our style, it feels fragile. These may be right, but they are judgement, and to the person on the receiving end they are fuel for a single conclusion, that you do not rate their work.

It is the difference between the MOT and the mechanic telling you he would never have bought this car in the first place. One is a pass or a fail and not up for debate. The other is him being sniffy, and you are free to ignore it. The trouble starts when he reads them out in the same flat voice and you cannot tell the law from the opinion.

When both arrive in the same message, the objective findings are contaminated by the subjective ones. The builder disputes the taste, quite reasonably, and the dispute swallows the credential. The one finding that genuinely had to be dealt with drowns in an argument about a naming convention.

This is a compression problem: “this needs changes” is a small token the sender has packed with checkable facts, and the receiver unpacks as a verdict on them.

The design that fixes it is stricter than what it replaces, though at first it looks like leniency. Approval depends only on the things a machine or a checklist could confirm and that nobody could reasonably argue with. Everything else, every matter of pattern and structure and taste, is advice, offered clearly as advice, which the builder is free to decline. Two channels, marked as two channels, carrying different weight. This makes the gate unarguable, because there is no opinion left in it to argue with, and it removes the sting, because the part that felt like judgement no longer has the power to block you.

There is a cleaner way to name the two kinds of claim. The objective checks are a contract: explicit, verifiable, reproducible, needing no shared taste to interpret. The advice is a concept: compressed, unpacked differently by each person who receives it, and only useful where approximate agreement is enough. The error that causes the damage is running a concept through a channel that required a contract, and in an organisation that error does not merely confuse people, it makes them feel personally slighted while it does so.

Be honest that the boundary is not perfectly clean. “This is unmaintainable” sits in the grey, and real engineering judgement lives there. The fix survives the fuzziness anyway, because it does not need every case to be clearly objective. It only needs the clearly-objective set to be enough to gate on, and it is. Secrets, failing tests, known-vulnerable dependencies, missing rollback, unscoped access to production: that set alone catches very nearly everything that actually hurts. Everything past it can be advice without the sky falling.

The colleague I saw resist all of this hardest was not, when you listened, arguing against being checked. They were arguing against being judged. The two had never been separated for them, so they fought both as one.

What they should have been given

So the answer to secrets in public repositories is not to teach everyone to run a scanner. It is that the path they were already standing on should have been running it, quietly, before the mistake had a chance to become one. The control is inherited by standing on the path, not implemented by each person who walks it. It is the bumpers in a bowling lane: you did not put them up, you might not even clock that they are there, but they are quietly making the gutter a very hard place to end up. This is the argument I made about governance review, turned to face a different room. There the certified parts were pre-cleared building blocks that made an artifact compliant by construction. Here they are the apparatus of safe software, delivered the same way.

Concretely, you provide by default what you would otherwise instruct: secret scanning at commit and again on the server, dependency and vulnerability checks on every push, a starting template that already has continuous integration and tests and a rollback path, access scoped sensibly out of the box. The tools of the trade, all of them, still required. Trufflehog and Snyk and static and dynamic analysis do not go away because the author is new; they matter more. What goes away is the expectation that the author knows how to set any of it up. The measure of the whole programme is how little the builder has to know is there.

And there is a turn in this that is new, and that the earlier version of the argument did not have. The instructions on the golden path are not written for the person. They are written for the model that is helping the person build. You hand the assistant the standards, the architectural preferences, the approved libraries, the shape of a compliant repository, as context it reads before it writes a line, and it steers the builder onto the road without the builder ever having to learn the road. The path stops being a set of rules a human is asked to follow and becomes the ambient knowledge of the thing doing the work. That is as close to invisible as a control can get.

None of that removes the case for a few useful habits, offered as habits and not as a gate:

Decide out loud whether the thing is a toy or a tool. It is a fork in how much you owe it, not a step in a process, and most things can stay toys forever. But the moment you post it somewhere for others to use, you have answered the question, and it is a tool now.

Have the model write the tests, and then read the tests rather than the code. You cannot meaningfully review two thousand generated lines. You can read thirty test names and check that they describe the behaviour you actually wanted, which turns verification back into a ten-minute human task and is the most practical answer going to the problem of owning code you did not write.

Ask the model what it left out. “Review this” produces flattery, roughly the calibre of feedback you get asking a Labrador whether you are a good person: an ecstatic yes, a great deal of tail, no actual information. Ask instead: “what are the three most likely ways this fails when someone else runs it, what did you assume, and what did you skip”. It is the same move as asking any source to mark the edge of what it knows, pointed at code.

And keep what you asked for, not only what you got. The prompt history is the design rationale. In six months the code will be opaque, but “I asked for this because that” is the difference between a thing you can maintain and a thing you have to rebuild.

The reframe under all of it, the one that would have saved most of these arguments before they started: a review is the cheapest bug-finding you will ever get, not a gate you have to pass. Twenty minutes of a colleague’s fresh attention finds things you cannot, because you are holding the model of what the code should do and they are not. Same activity, opposite feeling, depending entirely on whether it arrives as help or as a verdict.

Where this leaves it

The first hard part is genuinely solved, and I do not want a word of this read as regret about that. Letting the person who understands the problem build the answer directly is one of the better things to happen to software in a long time, and the answer to its risks is not to take the tools away from them or to gate them until the joy is gone.

The second hard part was always the larger one. It did not arrive with AI; it was only ever hidden behind the skill that AI made cheap. It is still here, in full, and it is now being met by people who were never shown it existed. Shaming them onto a harder path is the wrong instinct. The better one is to build the path so that the discipline of the last twenty-five years arrives with the tools, inherited and invisible, and to keep the checkable apart from the merely preferred, so that being checked need never again feel like being judged.