↓ Skip to main content

Who Maintains the Slop?

A coworker ships a feature this week. The demo goes well, the metric moves, the credit lands on them. Six months later the feature breaks, and the ticket is assigned to you. You did not write it, you did not ask for it, and the person whose name is on the commits cannot explain it to you, because they did not write it either. They prompted it, you inherit it, and that asymmetry is the whole problem.

The whole handoff runs like this:

Ledger: generated code sends credit back to the generator and costs forward to a maintainer who never consented and pays the full cost of understanding

The slop handoff is not the ordinary handoff, where someone moves on and leaves their code behind. It is a new and worse kind, because the thing being handed off carries less recoverable intent than human code ever did, and the person handing it off has less of that intent to offer than the author of human code ever had. The slop producer walks away with the reward. The maintainer is left with the cost, and was never asked whether they wanted to absorb it.

Generation Is Cheap. Understanding Is Not.
#

The economics of AI-assisted code are lopsided in a way most teams have not priced in. Generating code is now nearly free, a prompt and a few seconds. Maintaining code has gotten cheaper too, because you can now ask a model to explain the code to you, but it has not gotten cheaper by nearly as much. Maintenance is dominated by understanding, and understanding, even with help, is still time-consuming in a way generation is not.

When the cost of producing something collapses while the cost of owning it stays fixed, you get a predictable result. People produce a great deal more of it than they are willing to maintain. This is the same dynamic economists call a negative externality: the producer captures the benefit and passes the cost to someone else who never agreed to pay it. In software, the river the cost gets dumped into is the codebase, and the people downstream are the maintainers.

The old discipline that held this in check was effort. Writing a feature by hand cost days, and that cost forced a conversation about whether the feature was worth building and whether the author was ready to live with it. Remove that cost and the conversation disappears, because the friction that produced the conversation is gone. The feature still costs days to maintain. It just costs someone else, later, in a currency the original author never had to spend.

The Author Is No Longer the Cheapest Maintainer
#

For a long time, software engineering ran on a principle that aligned incentive and responsibility cleanly. Amazon stated it as you build it, you run it: the team that creates a service operates it, because they understand it best, and making them own the consequences makes them answerable for what they ship.

That principle worked for a specific reason. The person who wrote the code was also the cheapest person to maintain it, because the act of writing it left a durable model in their head. “You build it, you run it” was efficient as well as just. The accountability aligned with the economics.

AI slop severs that alignment. The person who generated the code did not build a model of it in their head, because they did not build it. They saw the output, checked that it ran, verified that it did what they wanted, and moved on. They are not the cheapest maintainer anymore. Often nobody is. The maintainer who inherits the code knows neither the original intent nor the code itself, and the author who might remember the intent has nothing to say about the implementation that would help.

So the natural loop closes wrong. Instead of the builder owning what they built, the code drifts to whoever is left, whoever is downstream, whoever still cares about the codebase staying healthy. Maintenance becomes a tax on the people who care, levied by the people who do not.

The Defining Feature of Slop Is Missing Intent
#

It is worth being precise about what makes slop slop. It is not only that the code is bad, because sometimes the code runs fine and passes its tests. It is that the code carries no intent anyone can recover.

Human code, even messy human code, is full of traces of the mind that wrote it. Variable names that betray a mental model, comments that record a half-thought, a function split in a way that reflects how the author decomposed the problem. These are imperfect signals, and they are often misleading, but they are signals. When you inherit human code, you are an archaeologist working through strata left by a civilization that existed. This is what The Code You Will Never Read describes from the maintainer’s side: a growing body of code whose internals are opaque by construction.

AI slop is archaeology without the civilization. The patterns in it were not chosen by a mind that held the problem; they were chosen by a model selecting the most probable next token. The names are plausible, the structure is conventional, and none of it is evidence of a decision you can reconstruct, because no decision was made in the way a human makes one. You cannot ask the code what it meant, and increasingly you cannot ask the author either, because the author’s answer is the same guess you would make yourself.

Missing intent is what makes AI slop harder to maintain than the human mess it resembles. The mess is not the hard part. The hard part is that the mess came with nobody attached to it.

The Missing Ingredient Is Consent #

The sting in the slop handoff is not only that the work is hard. It is that the maintainer had no say in any of the decisions that produced it.

They did not choose to build the feature. They did not choose its scope, its boundaries, its dependencies, its tradeoffs. They did not choose the abstractions it imposes on the codebase, or the patterns it will invite the next generator to copy. All of those choices were made by a person and a model who will not be present when the consequences arrive, and they were made without the person who will carry the consequences in the room. The maintainer inherits a set of decisions they were never party to, and is asked to own outcomes they could not influence.

The missing consent is the part that goes beyond “the task is annoying,” and it is why slop handoffs corrode teams faster than ordinary ones. I wrote about the motivation tax on unchosen work in The Cost of Work You Did Not Choose. The slop version of that tax is heavier, because the work is harder to understand and the handoff is more frequent. When being handed other people’s AI output becomes the norm, the people who maintain the codebase start to feel like janitors in a building they did not design and are not allowed to redesign, and that feeling is a leading indicator of people leaving.

The Generator Is Rational, Not Villainous
#

It is tempting to tell this story with a villain, the lazy coworker dumping their slop on the team. That story is satisfying and it is mostly wrong, and believing it will stop you from fixing the actual problem.

The generator is responding rationally to the incentives the team has set. The team rewards shipping. It does not charge for maintenance. It does not require ownership to persist after a feature lands. It does not ask whether the maintainer consents to absorb the code before it merges. Given those incentives, generating fast, shipping fast, and moving on is the behavior that gets rewarded, so it is the behavior that happens. You do not fix an incentive problem by asking people to be better. You fix it by changing the prices.

The mistake is to moralize what is structural. The slop producer is not failing at professionalism. They are succeeding at exactly what the system measures, and the system measures velocity, not the maintenance debt that velocity leaves behind. Until the system charges for that debt, the generator who slows down to own their output is not virtuous; they are slower than the colleague who does not, and they lose.

What to Do
#

The fix is to move the price back to where the benefit was captured. A few concrete moves, each of which closes a gap the current default leaves open.

Make intent travel with the code. The maintainer should inherit a specification, acceptance criteria, and the decisions that matter, not just a diff. When a feature lands, it lands with a written account of what it was for, what it was not for, and where the hard choices were made. The code is downstream of the spec, as Defects Flow Downstream, Fixes Must Flow Upstream argues, and the spec is the part that lets a future maintainer fix the source instead of patching the symptom. If the generator cannot produce that account, that is the signal that the generator did not understand what they shipped, and the merge should wait until someone does.

Make the generator sticky. The person who generates the code owns it for a window after it ships, the same way a human author would under “you build it, you run it”. You prompt it, you run it. Bugs in that module route back to the generator for a quarter, not to whoever happens to be nearby. This restores the alignment that cheap generation removed: the person who captured the benefit now carries at least the first round of the cost, which is the cheapest place to charge it.

Require the maintainer’s consent. No AI-generated change lands on a surface someone else owns without their sign-off. This is the one that feels heavy and is the most important, because it is the only move that gives the maintainer a say in the decisions that will become their problem. The owner of the affected code reviews the intent and the boundaries, not the diff. If they would not have chosen to absorb the change, they should not be forced to, and forcing them is what produces the slop handoff in the first place. This is the same intent-source question that decides who is qualified to act on a change in Who Resolves the Merge Conflict?: the person who can back the decision with intent they actually hold.

Price the handoff. If a slop producer does hand work to someone else, they owe that person context, tests, and time, not a working diff. The handoff is not complete when the code runs. It is complete when the receiver could explain the code to a third person without the original author in the room. Charge the handoff in the currency the generator tried to skip, which is understanding, and watch how much less slop gets produced when understanding is the cost of walking away.

Gate generation on ownership. Before AI code can merge, there must be a named owner who will maintain it. No owner, no merge. This is a trivial rule and it eliminates the worst cases outright, the drive-by generation that lands in a shared module and becomes everyone’s problem and no one’s responsibility.

The Real Failure Is Upstream
#

The pattern underneath all of this is the one Rethinking Code Review in the Age of LLMs reaches from a different direction: real responsibility lives upstream, in the decisions that guided the work, not in the person left holding the diff at the end.

The slop handoff is what happens when responsibility is allowed to drift downstream and settle on whoever is standing there when the code breaks. The generator made the decision to generate. The model made the decisions inside the code. The maintainer made no decision at all, and gets to own every one of them.

That is not a sustainable arrangement, and the people who notice it first are the maintainers, because they are the ones paying the tax. They will not keep paying it quietly. They will either push the price back upstream, where it belongs, or they will leave for a codebase where someone else has already done it.

AI did not invent the handoff. It made generation so cheap that handing the result off became the path of least resistance. The slop producer is not a villain; they are a rational actor in a system that prices generation at zero and maintenance at full cost. The fix is not to scold them. It is to put the price back where it was captured, in the currency that was skipped, which is understanding.

See also
#

References
#


Zero Touch Engineering

“Zero touch” has been a goal in software-adjacent fields for over a decade. Networks provision themselves, deployments ship on green, and operations teams have been shrinking toward zero. The one stage that resisted zero touch was the engineering itself: deciding what to build, writing it, and deciding it is good enough to ship. That is the part LLM agents are now closing.

Zero Touch Engineering (ZTE): a change travels from an observed signal to a deployed fix with no human keystroke, no human review, and no human approval in the path. Zero touch engineering has a clear lineage in networking and operations, and that lineage explains why software engineering was the last holdout and what it takes to close the gap.

Where “zero touch” already lives
#

The phrase has a precise home, and it is not software development.

Zero-touch provisioning (ZTP) remotely configures network devices (switches, routers, access points) with no per-device manual setup, standardized by the IETF as Secure ZTP in RFC 8572. Plug a device in and it fetches its configuration, authenticates, and joins the network on its own (Wikipedia, “Zero-touch provisioning”).

The telecom world generalized the idea into ETSI’s Zero-touch network and Service Management (ZSM), a standards group formed in 2017 with the explicit goal of “100% automation” of operational processes, now actively working on closed-loop, AI-agent-driven architectures.

Closer to software, two more ideas occupy the same ground. NoOps, coined by Forrester in 2011, is the vision that IT operations becomes so automated that developers never need to talk to an operations engineer again. Continuous deployment removes the last human gate from delivery: every change that passes its checks goes to production, automatically.

Notice what these four have in common. Every one of them automates a stage that is deterministic. Given a desired state and an event, the correct action is fully specified. Provision this device. Deploy this artifact. Page this on-call. There is no judgment left to encode, because the procedure was already mechanical and we just had humans performing it.

That is exactly why none of them touched engineering. Engineering is the stage that is not deterministic. Deciding what to work on, judging whether a change is worth shipping, choosing between two reasonable designs: these are judgments, not procedures. You cannot automate a judgment you cannot first write down.

What “zero touch” actually requires
#

Most discussion of AI in software focuses on the wrong stage. It focuses on the agent writing code. That is the easy part, and it was never the part that made a process touchless.

A touchless process is a closed loop.

flowchart LR
    Observe --> Decide
    Decide --> Implement
    Implement --> Verify
    Verify --> Ship
    Ship --> Learn
    Learn --> Observe

Every stage must run without a human, and the output of the last stage must feed the first. Continuous deployment automates Ship. NoOps automates the runtime half of Observe and Learn. ZTP automates a specific kind of Implement. ZTE is what you call it when the entire loop closes, including Decide, the stage that requires judgment.

The agent writing the code is one sixth of the loop. If you automate Implement and leave a human approving the result, you have an efficient assistant, not zero touch engineering. The “zero touch” claim only becomes true when a bug report can become a production fix with no human at the gate, and when the system decides for itself that the fix is worth shipping.

Why engineering was the holdout
#

The reason ZTE lagged ZTP by a decade is not that writing code was hard. Writing code was always the most automatable part of engineering, which is why templates, code generation, and scaffolding existed long before LLMs.

The holdout was Decide.

Deciding what to work on is a sequence of judgments: which problems are worth solving, which are urgent, which should be declined, which need an architectural change versus an incremental fix. As I argued in The Shifting Bottleneck, every time AI removes a constraint at one stage, the next constraint appears one level higher up the decision chain. Producing code dissolved into verification. Verification dissolved into deciding what to implement. Deciding what to implement dissolved into deciding what to build.

The bottleneck climbed until it landed on the one thing that could not be mechanized: judgment about direction. That judgment is the gate ZTE has to remove, and removing it is a different kind of change from removing a deploy button. A deploy button is a procedure. Direction is taste, context, and tradeoff.

So ZTE is not a tool you install. It is a measure of how much of your engineering judgment you have managed to make explicit.

ZTE is proportional to encoded judgment
#

The encoded-judgment measure gives a useful test for how close a team or project is to zero touch engineering. Measure how much of the decision loop is encoded versus sitting in someone’s head.

The projects closest to ZTE encode five things: the same five I described in The Self-Evolving Repository, because that article was about ZTE without using the word.

A machine-readable roadmap that lets the system distinguish work that matters from work that does not. Most triage decisions are not subtle architecture calls. They are straightforward: this bug affects users, fix it; this request is out of scope, decline it; this dependency has a vulnerability, patch it. If those decisions are written down, the system can make them.

A verification pipeline that replaces human review with multiple independent layers: tests, static analysis, property tests, mutation tests, scenario validation, adversarial probing. Human review of agent-written code is the lowest-leverage activity in the loop, as I argued in Rethinking Code Review in the Age of LLMs. You do not trust the code. You trust the verification system.

A decision policy that ranks competing work the way a competent maintainer would.

Guardrails that bound the blast radius: budget limits, rollback on regression, escalation when a fix loop appears.

A learning loop that turns every failed change into a future constraint.

When all five are in place, the loop closes and the process is touchless. When any one is missing, a human has to step back in at that gap. The degree of zero touch is exactly the degree of encoded judgment, nothing more.

Where the human re-enters
#

A fully closed loop still has a leak, and the leak is direction.

A ZTE system that only reacts to observable signals will optimize for whatever those signals measure. Bug reports as the only signal produce a system excellent at fixing bugs and terrible at anything else. Feature requests as the only signal produce a system that accumulates features and loses coherence. That drift is Goodhart’s law applied to engineering: when a signal becomes the target of an autonomous system, it stops being a good signal.

The roadmap is what counteracts the drift, but the roadmap itself goes stale. User needs shift; the ecosystem moves; a roadmap written in January can be wrong by July. Updating the roadmap means making a judgment about what the project should become, and that judgment is the one thing current models can approximate but not fully replicate.

The pragmatic answer is not to solve the direction problem perfectly but to bound it. Let the system make small direction adjustments based on observed signals. Require large directional changes to pass through a human. That split keeps the loop touchless for the vast majority of decisions while preserving human oversight for the small fraction that set long-term trajectory.

The human in a ZTE system does not write code, review changes, or approve deploys. The human authors the system that does all of those things, and intervenes only when the system’s judgment and the project’s direction diverge. That is a different job from the one most engineers have today, but it is the job ZTE leaves behind.

The real question
#

The networking world reached zero touch because the stages it automated were procedures. ZTE is the claim that the remaining stages, the judgment stages, can be made procedural enough to automate too.

That claim is only partly true. You can encode most engineering judgment, enough to close the loop for routine work. You cannot encode all of it, and the part you cannot encode is exactly the part that determines whether the project moves in a direction worth moving.

Zero touch engineering is not about removing humans from writing code. It is about discovering, precisely, which of your judgments were ever more than procedure, and which were just procedure you had not bothered to write down yet.

See also
#

References
#


The Code You Will Never Read

There is a kind of code growing around us, and growing fast. Code that no human wrote, line by line. Code that no human will read, line by line. Code that no human will review, because the verification system passed and the change shipped on green. If you are an engineer, this is supposed to make you uncomfortable. But the discomfort is something you can get past, because we have gotten past it before, in several fields next door, and their tricks transfer.

Machine learning practitioners have been comfortable with opacity for a decade. The trained neural network sitting behind your favorite model is, at the bottom, billions of floating-point numbers. Nobody reads them. Nobody can. There is no “go to definition” for a weight. You cannot trace a decision through the layers by opening the matrix in your editor and following the logic. The entire artifact is illegible to a human by construction, and the field that built it made its peace with that a long time ago.

The code now being produced by agents is heading toward the same property, and the comfort ML people found is the comfort software engineers need to find next.

The Model We Already Live With
#

How did machine learning get comfortable with a thing nobody can read?

Not by pretending to understand the internals. By refusing to need them.

The model is a black box. You understand it through what it does, never through what it is. You probe it with inputs and watch the outputs. You assemble a test set that captures the behaviors you care about, the happy paths and the adversarial ones. You measure accuracy, calibration, latency, and failure modes on the edge cases that would embarrass you in production. You characterize the artifact from the outside, and the characterization is the thing you trust.

Nobody on a model team ever says “let me read the weights to see if this is correct.” That sentence is nonsense in that world. It is becoming nonsense in ours, and the people who notice last will be the ones still trying to read code that no longer rewards reading.

Code Is Becoming That
#

The parallel is not exact, but it is close, and it is closing.

A neural network is opaque because its meaning is smeared across billions of parameters, none of which means anything alone. Agent-written code is opaque for a different reason and a more mundane one: there is simply too much of it, it was produced too fast, and no human has the hours to reconstruct what it does from the source.

The end state is the same from the operator’s point of view. You are handed an artifact that does something. You cannot hold its behavior in your head by reading it. You have to find out what it does the way you find out what a model does, by running it and watching.

This is already the lived reality for the engineers defending codebases where agents produce faster than humans can read (The Codebase Gardener describes that arithmetic). It will be the reality for everyone soon enough. The code you depend on, the code in your dependencies, the code that ships from the team across the hall, is increasingly code whose source you will never open, because opening it would tell you less than running it would.

Why Code Felt Different (And Why It Should Not)
#

Source code used to be the thing that made software tractable. That was the whole promise. Unlike a compiled binary, unlike a trained network, you could open the file and follow the logic. Reading was how you understood a system, how you debugged it, how you trusted it. The fact that code was legible was the foundation engineers built their competence on.

Machine learning never had that foundation, so it never grieved losing it. Software engineering did, and does, and that grief is most of what the discomfort is made of.

Strip the grief away and the practical question is simpler. If you could not read the code anyway, would you rather have no code, or would you rather have code you can probe, test, measure, and roll back? You would rather have the code, and you would build the same scaffolding around it that ML built around its weights. The only thing standing between you and that scaffolding is the feeling that you ought to be able to read it, and that feeling is a habit, not a requirement.

The Skills Transfer Directly
#

The practices machine learning developed for living with opacity are almost a one-to-one map onto opaque code. You already know half of them, because software engineering reinvented them under different names.

In ML, the test set captures the behaviors that matter. In opaque code, that is the acceptance criteria written before implementation, plus a characterization suite that records what the code actually does across the inputs you care about (Verifying Code Without Reading It is the full version of this system).

In ML, probing with held-out and adversarial inputs is how you find where the model breaks. In opaque code, that is property-based testing and fuzzing, generating inputs no human would think to write, exposing the edge cases reading would have missed anyway.

In ML, you measure accuracy and failure rate on a benchmark, not intuition. In opaque code, that is defect escape rate, rollback rate, time-to-detect, and change failure rate, measured per change, in production.

In ML, adversarial examples are how you stress the model before it ships. In opaque code, that is the adversarial pass: a separate agent whose only job is to break the change, with no incentive to approve (Verifying Code Without Reading It covers the separation that keeps it sound).

In ML, the model card documents what the model is good at and where it fails. In opaque code, that is the specification plus the issue, the artifact that tells you what the code was supposed to do, which matters far more than what any individual line does (Rethinking Code Review in the Age of LLMs).

Every tool you need to trust opaque code is a tool ML already built to trust opaque weights, renamed.

It Is Not Just Machine Learning
#

Machine learning is the most recent field to face this problem, not the only one. Depending on an artifact you cannot fully read is an old situation, and the disciplines that met it first each invented a piece of the answer. None of them solved it by making the artifact legible. They solved it by changing how they interacted with it, and every one of those changes is a tool we can pick up for opaque code.

Silicon. Once a chip is fabricated, you cannot read its logic from the silicon. The hardware field answered with design for test: boundary scan, built-in self-test, test vectors injected at the pins and observed at the outputs. The lesson is structural, and it is the one our field is slowest to learn: testability has to be designed into the artifact before it exists, not bolted on after. For code, that means the architecture carries probes, hooks, and test seams by default, so an implementation nobody reads can still be exercised by a system that does.

Pharmacology. A drug interacts with a body no one fully models. The field answered with phased clinical trials, a tiny reversible exposure first, then efficacy, then population scale, all run double-blind and followed by post-market surveillance. The transferable pattern is staged rollout with independent evaluation and production watchfulness, which is exactly canary deployments, feature flags, a verifier separate from the author, and monitoring that treats production behavior as the real verdict.

Behaviorism. Psychology met an opaque artifact earliest of all, the mind itself, and built a whole epistemology around it. Stimulus and response, operational definitions, the refusal to introspect what could not be opened. The stance is the one this article is arguing for: when you cannot inspect the internals, you understand the thing by characterizing what it does, and you stop treating that as a compromise and start treating it as the method.

Cryptography. A cipher is trusted not by reading it but by trying to break it, and Kerckhoffs’s principle says the system must stay safe even when its mechanism is fully public. The lesson is an inversion that cuts against the instinct to trust illegible code by hoping someone read it: obscurity was never what made it safe, and dropping the reading does not drop the safety if the adversarial testing holds. Trust comes from attack, not from inspection.

Optimizing compilers. The output of a modern optimizer is illegible to humans, and no one considers that a crisis. Compilers are trusted through conformance suites and fuzzing, not by reading the assembly they emit. Generated code is a new instance of a situation our own field has always been calm about, a transformation pipeline whose output you verify rather than read.

Legacy mainframes. Long before LLMs, our field operated systems no living person fully understood, COBOL cores kept alive by runbooks, golden-file tests, and behavior contracts. The practitioners did not resign over the illegibility. They built characterization tests that pinned observed behavior, and they ran the systems safely on those pins for decades. That is the exact toolkit an opaque, agent-managed codebase needs.

Each field contributed a distinct piece. Hardware taught us to design testability in beforehand. Pharmacology taught us staged exposure with independent oversight. Behaviorism taught us the epistemology of characterization. Cryptography taught us to trust by attack, not by inspection. Compilers taught us that illegible output is normal and suite-verified. Legacy systems taught us to operate on pinned behavior when comprehension is gone. Stack those techniques and you have the full practice for a codebase no one reads: instrumented by design, rolled out in stages, characterized by behavior, stress-tested by adversaries, suite-verified, and pinned by contracts.

Stacked, the six lessons assemble into a single practice:

flowchart TD
    HW[Silicon designs testability in before the artifact exists] --> P[Practice for a codebase no one reads]
    PH[Pharmacology stages exposure with independent oversight] --> P
    BE[Behaviorism characterizes what a thing does instead of opening it] --> P
    CR[Cryptography trusts by attack rather than inspection] --> P
    CO[Compilers treat illegible output as normal and suite-verified] --> P
    LE[Legacy systems operate on pinned behavior when comprehension is gone] --> P

What Understanding Means When You Cannot Read
#

Engineers are going to resist this, so it is worth being precise about what is lost and what is gained.

Reading code gives you one kind of understanding, causal and local. You trace a branch, follow a call, and build a mental model of why the code behaves the way it does on the inputs you happened to trace. It is deep, but it is narrow. It covers the paths you followed, and it depends on you being alert and unhurried while you followed them.

Characterizing behavior gives you a different kind of understanding, statistical and global. You cannot say why a specific input produces a specific output by tracing the logic. But you can say, with evidence, how the system behaves across thousands of inputs, including the ones no human would have thought to trace.

The first kind feels more satisfying because it produces a story. The second kind is more reliable because it does not depend on which story you happened to follow on the day you read it.

A reviewer who reads a diff understands a few paths well and the rest not at all. A test suite that runs on every change understands every path it covers, every time, forever, without getting tired. Reading gives you a vivid understanding of a tiny fraction of the behavior. Probing gives you a coarse understanding of all of it, and for software that has to keep working when you are not looking, the second is the one that compounds.

The Identity Problem
#

Here is the part nobody puts in the engineering blog posts.

This shift is hardest for the people it should be easiest for. The senior engineer, the staff engineer, the person whose entire professional identity is built on being able to open any file in the codebase and understand it, is the person being asked to surrender the exact skill that made them senior.

That is a loss, and it is real, and pretending it is not is why so many of the arguments against unread code sound rational but run on fear. It is not irrational fear. If the thing you are best at is reading code, and reading code stops being the valuable thing, then you are being asked to become a beginner again, and beginners are slow and uncertain and uncomfortable.

The way through it is the same way ML practitioners found. You do not stop being valuable. You move your value up a layer. The ML engineer’s skill was never reading weights, it was designing the training, choosing the objective, building the evaluation that decided whether the model was good enough to ship. The senior engineer’s skill, it turns out, was never really reading lines either. It was knowing which behaviors matter, what the failure modes are, where the blast radius lives, and what “done” actually means for this system. Those survive the loss of legibility intact, and they are exactly the skills the opaque-code world pays for.

You are not losing your competence. You are being asked to point it at the layer where it was always doing the most work.

How to Get Comfortable
#

Comfort is not an attitude you adopt. It is a confidence you earn by watching the safety net catch things. Each time a gate stops a bad change, each time a canary surfaces a regression a reader would have missed, each time a rollback undoes a problem in minutes, the illegible artifact gets a little less frightening, because the system around it is doing the job your eyes used to do.

Start where the stakes are low. Pick the changes you were never going to read carefully anyway, the small, reversible, low-blast-radius ones (You Already Review Code Without Reading It is the uncomfortable proof that this is most of them). Let those merge on green, with no human in the middle, and watch what happens. If nothing breaks, your nervous system learns faster than your opinions do.

Then build the probing muscles. Write the behavioral tests before you let an agent write the code. Add a fuzzer to the paths that carry real risk. Put a blast-radius classifier on the gate, and reserve your attention for the small set of changes that actually deserve a human, the irreversible and the trust-boundary-crossing. Measure the outcomes, and let the measurements argue for you when your instincts object.

The end state is not that you stop caring about code. It is that you stop needing to read it to trust it, the same way the ML engineer stopped needing to read weights to trust a model. You develop a feel for the system the way they developed a feel for the model, by living with its behavior, watching it under load, and letting the evaluation be the authority instead of your eyes.

The Disciplines That Already Did It
#

Machine learning is the closest mirror to what software is becoming, and it is worth noticing how comfortably it ended up.

Nobody in ML talks about the illegibility of weights as a crisis. They talk about evaluation, about distribution shift, about calibration, about the gap between benchmark and production. The opacity is settled ground, the same way it is settled ground in chip design, in pharmacology, in every field that learned to trust an artifact it could not open. The work happens entirely at the boundary, in the inputs you choose and the outputs you measure, because that is where understanding of an opaque artifact can live.

Software engineering is arriving at the same place, later and more reluctantly, because it had something to lose that those fields never had. The legibility of source code was a gift, and it was a gift that lasted a few decades, and it is ending, and the ending feels like a demotion when it is really a relocation.

The code you will never read is coming regardless. The only question is whether you learn to be comfortable with it the way half a dozen fields already are, by building the cage of tests and probes and staged rollouts and adversarial checks around it, or whether you keep insisting on reading until the volume of unread code makes the insistence irrelevant.

The first option is work. The second option is a feeling that does not scale. Pick the one that does.

See also
#

  • You Already Review Code Without Reading It - the diagnosis that most review already happens without reading, which is the proof that the comfort is mostly already earned.
  • Verifying Code Without Reading It - the concrete system of tests, critics, and gates that replaces reading, and that maps almost one-to-one onto ML evaluation practices.
  • Rethinking Code Review in the Age of LLMs - the case for moving human effort from the diff to the specification, which is the layer where understanding of opaque code actually lives.
  • The Codebase Gardener - the arithmetic that makes unread code inevitable, and the strategy for defending a codebase against entropy produced faster than you can read.
  • The Acceptance Gap - the root insight that a model cannot vouch for its own output, which is why the external evaluation (the ML equivalent of a test set) is the part that has to be trustworthy.

My AI Workflow: The Skills Are the Part That Compounds

When someone asks what my AI workflow looks like, the real answer is boring on the surface and surprising underneath. I use a couple of coding tools and a couple of models, and none of them is the thing I would miss. What I would miss is the body of skills I have been writing, collecting, and refining for the better part of a year.

This is a snapshot of that workflow as it stands today. The tools come first because they are the visible part, but the real argument is that the tools are the part I expect to replace, and the skills are the part I expect to keep.

I run two coding surfaces and two models, and neither is the interesting part of my setup. At home I use GLM 5.2 from z.ai, and at work I use GLM 5.2 fast from Fireworks. For editing I use Cursor at work and VS Code at home, and for anything agentic, multi-step, or long-running I use OpenChamber, which is built on OpenCode. I never open OpenCode directly; OpenChamber is the surface, OpenCode is the engine underneath. The part that actually determines the quality of my work is the library of skills I have been building and sharing in tomzx/agents, and those skills run inside all four of those tools. Everything else is interchangeable.

The setup, in one paragraph
#

Cursor or VS Code is where I read, navigate, and do small edits. The moment a task turns into a sequence of steps, create an issue, reproduce a bug, review a PR, open a PR, I move it to OpenChamber, because that is where the work can run to a natural stopping point without me holding the state. OpenChamber is built on OpenCode and is the control room on top of it: it gives me branchable sessions, worktree isolation, and a single surface to steer several sessions at once. The skills are not tied to any of these surfaces; because they follow the open Agent Skills format, the same skill runs in Cursor, VS Code, OpenCode, or OpenChamber, so I pick the surface that fits the moment and the workflow does not change. At home the editor is VS Code and the model is GLM 5.2 on z.ai, at work the editor is Cursor and the model is GLM 5.2 fast on Fireworks, and I switch between them without changing anything else about the workflow.

The model and the shell are commodities
#

A year ago I would have had a strong opinion about which model to use. Now the models are close enough that the choice is mostly about latency and price, which is why I let the environment pick: z.ai at home, Fireworks at work. The model stopped being the lever once the models got good enough to follow a well-specified workflow reliably.

The same is true of the agent shell. Cursor, VS Code, OpenCode, and Claude Code can all read a repository, edit files, and run commands, and because the skills follow an open format they run in any of them unchanged. I use several of them because they are good at different things, but I do not expect any one of them to be the source of my advantage. The shells will keep improving, the models will keep swapping in, and my workflow should not have to move when they do.

The skills are the workflow
#

Everything that matters lives in tomzx/agents, a library of composable skills written in the open Agent Skills format. A skill is a self-contained SKILL.md with step-by-step instructions for one repeatable task. A compatible agent discovers skills by name, loads them on demand, and invokes them as slash commands like /create-pr, /review-pr, or /end-day.

The backbone is the SDLC pipeline. A high-level skill like /sdlc orchestrates dozens of focused sub-skills into a full path from issue creation through requirements, specification, plan, implementation, tests, pull request, review, documentation, and learnings. Each sub-skill is small enough to read in a minute and to improve independently. When I improve the review-pr skill, every flow that calls it gets the improvement, and I never have to re-explain that step to a session again.

The versioned skill is the difference between prompting and engineering a workflow. A prompt is a one-off conversation. A skill is the same conversation, written once, versioned, reviewed, and incrementally improved. The agent performs it the same way every time, and the library gets better as the workflows mature.

Where the human time goes
#

The SDLC pipeline is long, but my time is not spread evenly across it. Almost all of my attention goes to the top: needs, requirements, and specification. This is where defects fan out the widest and where fixes compound the longest, as Defects Flow Downstream lays out, and it is where the work stays irreducibly human, deciding what to build and what “done” means. A precise specification is the input every downstream skill consumes, and Software Engineering Teams in the Age of AI argues it is now the highest-leverage skill in the field.

Everything below the specification, I automate as far as I can. The plan, the implementation, the tests, the PR description, the review, the deployment, each is a skill the agent runs, and each is a place I would rather spend a token than an hour. Once judgment is encoded into the layer every change passes through, fighting over each unit of output by hand stops paying for itself.

The downstream hour that used to go into reading diffs goes somewhere else entirely: into building high-fidelity test environments that approximate the production environment as closely as I can get them. I would rather spend an hour making the test environment catch the bug than an hour reading a diff hoping to catch it myself, because the test environment runs on every future change and the diff reading runs once. Spending the hour on the environment instead of the diff is the trade Verifying Code Without Reading It and Rethinking Code Review in the Age of LLMs both argue: review is a one-time signal, verification against a production-like environment is a compounding one.

The encoding loop
#

The hardest problem with any workflow is not doing it. It is remembering to do it, every time, in the right order, without skipping the boring step.

My rule is simple. Every time I catch myself remembering to do something, that is a skill that should exist. The reminder to check for an existing PR before starting work, the instinct to link an issue to its plan, the habit of running the linter before committing, the reflex to write a learnings note after a painful bug, each of these was once tacit knowledge that lived in my head and showed up only when I was fresh. Now each one is a skill, and the agent runs it whether I am fresh or not.

The trigger is the remembering itself. If I had to remember it, the agent should not have to. Encoding it converts a forgettable step into a reliable one, and a reliable one into an improvable one, because once the step is written down I can see it, review it, and make it better instead of re-deriving it from memory every session.

What changes when the workflow is versioned
#

Once the workflow lives in a repository, three things become true at once.

First, it is reliable. The agent triages the issue, plans the work, reviews the PR, and closes the loop the same way today as it did yesterday, because the steps are written down and not reconstructed from guesswork.

Second, it is improvable. When a step is weak, I edit one file and every session that passes through it gets better. The compounding is real in a way that prompting never compounds, because the improvement is captured instead of evaporating.

Third, it is shareable. The skills are public, so anyone on my team or on the internet can run the same workflow against their own repository. The workflow became an artifact I can hand to someone else, which is the test for whether you actually have a workflow or just a habit.

What I expect to keep
#

If you asked me which parts of this setup I will still be using in two years, the answer is easy. Not Cursor, probably not OpenChamber in its current form, and almost certainly not GLM 5.2. The models and the shells are moving targets, and betting on any specific one is a bet against the field.

The skills are the part I expect to keep, but I do not expect to keep them as they are today. They will evolve, and the more interesting bet is that they will shrink. Every skill exists to encode a judgment the model cannot yet make reliably on its own: when to check for a duplicate PR, what a good acceptance criterion looks like, which steps belong in a retrospective. As the models get smarter, often smarter than me at the specific subtask, those judgments stop being mine to encode and start being the model’s to make. The skill for them becomes redundant and gets deleted.

What persists is the slower-moving part: how I decide what is worth working on, how the phases of a feature connect, where the human checkpoint belongs. The tactical skills will collapse into the model. The skills are not a fixed asset I am accumulating; they are a temporary scaffold for the gap between what the model can do today and what it will do on its own tomorrow, and a good chunk of the work is knowing which scaffold to take down next.

See also
#

References
#

  • Mathieu Larose, “My AI Coding Workflow” - the article that prompted me to write this one
  • tomzx/agents - the skills library that is the subject of this article, public and installable
  • Agent Skills format - the open skill format that makes the library portable across agents
  • OpenCode - the terminal coding agent the skills target
  • OpenChamber - the OpenCode-native control room for parallel sessions
  • Cursor - the editor I use at work for reading and small edits
  • VS Code - the editor I use at home for reading and small edits
  • z.ai - provider of GLM 5.2, my home model
  • Fireworks - provider of GLM 5.2 fast, my work model

Model Collapse: When Code Models Train on Their Own Output

Every code-generating LLM in production today was trained on code written by humans. That fact is easy to forget, because the same models are now writing most of the code. If humans stop reading, reviewing, and refining that code, the next generation of models will have to train on the output of this one, and the research on what happens then is not reassuring.

Models Were Trained on Human Code
#

The large code models learned from enormous corpora of human-written code: public repositories, Stack Overflow answers, patches, and documentation. None of that code was perfect. Much of it was buggy, redundant, or obsolete. But it carried something that model output cannot manufacture: the accumulated signal of millions of programmers solving real problems under real constraints, correcting each other in public, and converging over decades on patterns that actually work.

That signal is the fuel. When you prompt a model and it produces a working function, it is not reasoning from first principles. It is predicting the next token from patterns absorbed out of human code, and those patterns were selected by reality, not by another model’s preferences.

The Trend Is Pulling Humans Out of the Loop
#

I have spent the last year arguing that human code review should shrink, that we should verify code instead of reading it (Verifying Code Without Reading It), and that the specification, not the diff, is where human judgment belongs (Rethinking Code Review in the Age of LLMs). I still believe that, at the level of a single team shipping a single product.

But there is a consequence I have been slow to take seriously, because it does not show up in any one team’s metrics. It shows up in the corpus. As review becomes automated and engineers read less of what the model writes, the public record of code becomes a record of model output, lightly curated by machines. The very thing the next models need to train on, human-written and human-corrected code, is becoming a smaller and smaller fraction of what exists.

What Model Collapse Is
#

This feedback loop has a name. Researchers call it model collapse, and the clearest statement of it is Shumailov et al.’s 2024 paper, “AI models collapse when trained on recursively generated data”.

The finding is stark. When you train a model on data produced by another model, and then train the next model on the first model’s output, and repeat, the model degrades. It does not degrade the way a person gets tired. It loses information about the tails of the distribution first, the rare cases, the minority examples, the edge behavior, while its performance on the average case can look fine for a surprisingly long time. Model collapse is hard to notice precisely because the headline metrics keep going up while the long tail quietly disappears.

The mechanism is statistical, not mysterious. A model that fits another model’s output is fitting a smoothed, averaged version of reality. Each generation of training rounds off a little more of the texture, the weird inputs, the unusual-but-correct solutions, until what remains is a narrow, over-represented center.

The preprint that introduced the term, “The Curse of Recursion”, works through the mathematics, and the Wikipedia article on model collapse lays out the two stages, early and late, and the open disagreement among researchers about how severe the real-world impact will be.

Why Code Is Especially Exposed
#

General model collapse is a concern for text and images. Code is a sharper case, for three reasons that compound.

First, the useful part of code lives in the tails. The happy path is easy. The value is in error handling, concurrency, boundary conditions, security, the obscure API, and the input that should never arrive but does. These are exactly the minority examples that collapse erodes first. A model that handles the common case and breaks on the edge case is the literal definition of early model collapse, and it is also the definition of code that passes review and fails in production.

Second, code that looks correct is easy to generate and hard to filter out. A plausible-looking function that contains a subtle bug is not obviously wrong the way a garbled sentence is. If nobody reads it, and the tests do not cover the edge case, it merges into the corpus and becomes training data for the next model, carrying its bug forward as if it were a pattern worth repeating.

Third, the volume is exploding. When a model can draft a feature in minutes, the amount of model-written code committed every day is growing faster than anyone’s ability to curate it. Public repositories are filling with generated code, and that code is the raw material for the next training run. The dead internet theory, originally a half-joke about the web filling with bots, is becoming literal for code.

What Might Happen Next
#

No one knows exactly how this plays out, because the loop has not completed a full generation at scale yet. But a few hypotheses are worth stating plainly, because they are testable and they point at where to look.

The quality plateau
#

Models keep improving on benchmarks for a while, then flatten. The plateau will not look like running out of compute. It will look like running out of signal. The bottleneck moves from the size of the model to the quality of the data, and the data has stopped improving because it is no longer being written by anyone who understands it. This is the same pattern I described in The Shifting Bottleneck: each constraint you remove reveals the next one, one level up.

The long-tail erosion
#

While the common case improves, the edge cases get quietly worse. Security vulnerabilities, race conditions, and incorrect handling of unusual inputs become more frequent, not less, because the training distribution has thinned exactly where those lessons lived. Benchmarks that measure average correctness will miss this. Benchmarks that measure adversarial or edge-case correctness will catch it, and they will be the ones to watch.

The grounding premium
#

Code with verifiable grounding becomes valuable. What matters is not who typed it but whether it was selected by reality: it ran in production, it passed tests against inputs no one hand-picked, it survived real failures and was corrected by them. A human-written snippet that never ran carries no signal a model could not invent. A model-generated snippet that ran under real load and survived carries signal that no amount of recursive training can synthesize. Labels like “ran in production without rollback” or “verified against a fuzz corpus” start to mean something, the way “organic” became a label worth paying for in food. Grounding begins to matter as much as data volume, and the organizations that can prove their code carried a real outcome get better models. Eventually the most valuable training corpus is the one you can prove was selected by reality, not merely produced by a model.

The verification-as-data loop
#

The only new ground-truth signal that does not depend on model output is execution. The most valuable training data stops being code at all, and becomes (specification, implementation, test result) triples, where the test result is measured by reality, not inferred by another model. Reinforcement learning from execution, where the reward comes from actually running the code, replaces scraping repositories as the dominant way to improve code models. A model is, at inference time, a function of its context, as I argued in The Importance of Context When Interacting with LLMs; at training time it is a function of its corpus, and the corpus is the part that is degrading.

The split
#

A gap opens between organizations that keep humans in the loop and those that do not. The fully automated shops gain short-term velocity but slowly poison their own tooling, because their internal corpus drifts toward generic, model-flavored code. The shops that keep humans reading, correcting, and rewriting code retain a source of fresh signal, and their models, or at least their use of models, stays sharper over time. The competitive advantage flips from speed to the quality of the signal you feed back into the system, which is the same conclusion the foundation argument reaches in The Foundation Predicts the House of Cards.

The Case That Code Improves Instead
#

Everything above assumes the only training signal is “imitate the previous generation.” For text and images that is roughly true, which is why model collapse is a real worry there. Code is different in one decisive respect: it has an oracle.

Code runs. It passes tests or fails them, type-checks or does not, compiles or crashes, and a fuzzer can generate inputs no human would think to test. That execution result is a ground-truth signal that does not come from any model, and it is the one thing the self-consumption loop cannot synthesize. Where collapse assumes the model learns only from its own output, execution lets it learn from reality, and reality does not degrade between generations.

This changes the arithmetic in several ways that point upward rather than down.

First, reinforcement learning from execution replaces imitation as the engine of improvement. The training unit stops being “a file someone committed” and becomes a (specification, implementation, test result) triple, where the reward is measured by running the code. A model rewarded for passing tests it has never seen is being pushed toward correct behavior, not toward the average of prior outputs, and that pressure does not loop back on itself.

Second, selection effects push the surviving corpus upward, not toward the mean. When generation is best-of-N, or when an adversarial verifier tries to break each candidate, the code that survives into the corpus is the code that passed real checks. A repository built from verified survivors can be cleaner than the human corpus ever was, because GitHub and Stack Overflow were always full of bugs that nobody ran. Filtering is the antidote to unfiltered collapse, and code is the one modality where filtering is automatic.

Third, the long tail can get better represented, not worse. Humans are lazy about edge cases. Fuzzers, property-based tests, and generated edge-case suites are not. If the training signal includes execution across millions of rare inputs, the model sees more of the tail than human-written code ever covered, and the long-tail erosion hypothesis inverts into long-tail reinforcement.

So the optimistic thesis is precise: code is the modality least susceptible to model collapse, because it alone carries an objective, automatic, infinite ground truth. The thing that makes code hard, that it has to actually work, is also the thing that protects it.

The catch is that the oracle only checks what you ask it to check. A passing test suite proves the code does what the tests cover, not that the tests cover what matters. Let the same model write the tests and the code, or let the test suite stay shallow, and execution stops being an oracle and becomes a rubber stamp. The virtuous loop holds only while the verification is sound, separate from generation, and broader than the happy path.

What Breaks the Loop
#

Even the imitation-only loop has an escape hatch, if real data keeps accumulating. Gerstgrasser et al. showed in “Is Model Collapse Inevitable?” that collapse is avoided when synthetic data accumulates alongside real data instead of replacing it. Their result is the weaker version of the optimistic case: as long as we keep adding fresh human code to the corpus, the model does not have to train only on its own output.

The catch is the word “fresh.” The mitigation only works if real human code keeps flowing into the training set in meaningful quantities. And that is exactly the input the current trend is starving. Every hour engineers spend prompting instead of writing, every diff that ships unread, every answer on Stack Overflow that goes unposted because a model answered it privately, shrinks the stream of new human signal.

So the practical question is not whether model collapse is possible. The research says it is. The question is whether we keep enough humans writing, reading, and correcting code, in public, to keep the real data flowing.

The Verdict Hinges on the Oracle
#

I have argued, and still believe, that a single team should not force a human to read every diff if its verification system is strong enough. What I no longer believe is that this is automatically a tragedy of the commons. It is a tragedy only if the only signal the field feeds back into its models is imitation of prior output. Execution is a second signal, and for code it is the stronger one.

The two futures run on the same variable: the soundness of verification. If verification stays grounded in real execution, adversarial, and separate from generation, the loop improves on its own, and humans reading code matters less every year. If verification is captured, shallow, or graded by the same model that wrote the code, the loop degrades, and no amount of human reading at the end will save it.

So the instruction is narrower than “keep humans writing code in public.” It is: keep the oracle trustworthy. Run the tests, fuzz the inputs, separate the verifier from the author, and treat every passing suite as a claim about coverage rather than proof of correctness. Do that, and generated code can keep getting better long after humans stop writing most of it. Fail to do it, and the degradation arrives on schedule.

See also
#

References
#


Rolling Out the Unread Review

The case for review without reading, and the system that makes it safe, can both be sound and still fail to ship. I am writing about the part that actually decides whether any of it lands: your team.

You can be right about review, right about the system that replaces review, and still lose, because you shipped the rollout as a decree instead of as a sequence. The rollout has its own logic, and it is not the logic of the technical argument. A system people do not trust is a system that does not run, regardless of how well it verifies.

Treat The Resistance As Information
#

The easy story is that skeptics are afraid of change, and that once they see the numbers they will come around. That story is mostly wrong, and believing it will cost you the rollout.

The people who push back hardest on automated review are often your most careful engineers. They have seen the bugs a tired reviewer caught by instinct. They have been the person blamed when something slipped through. Their objections are not noise to filter; they show where your system is weakest. Calling their discomfort “fear” loses the argument before it starts, because they can tell you are not listening.

Three Concerns, Three Answers
#

Behind “I do not trust the bot” are usually three distinct concerns, and each needs a different answer.

The safety concern: automated review will let bad code through. You answer the safety concern with measurement, defect escape rate compared between the automated lane and the human lane.

The accountability concern: when something breaks, there is no one to point at. You answer the accountability concern with ownership that lives upstream, in the specification and in the people who own the rules.

The learning concern is the one nobody says out loud. Review is how juniors grow and how the team stays connected to its own codebase. This loss is real, and it is the concern that will sink the rollout if you ignore it, because no defect metric addresses it.

Sequence The Exposure
#

You cannot go from “a human reads every diff” to “no human reads any diff” in one step. Decompose the automation into three things people react to very differently.

A bot that comments is basically a linter, and almost no one objects. A bot that blocks is accepted once the rules are legible and owned. A bot that approves is the change people resist most.

Ship them in that order, weeks apart, on a low-blast-radius lane first: documentation, tests, internal-only changes inside a single module. By the time you reach the approving bot, the team has weeks of evidence that the earlier layers work. You are extending trust that has been earned, not demanding it up front.

Make Every Decision Legible
#

People resist what they cannot inspect. Every automated action must state which rule fired, what it checked (the file and line, the threshold, a link to the rule definition), and who owns that rule.

“Complexity 18 over 15 in auth.py:42, rule COMPLEXITY_LIMIT, owner Priya” is something a skeptic can argue with. “Bot approved” is something a skeptic can only rage at. The first turns a black box into a reviewable system. The second turns your careful engineers into enemies.

Promote Humans To The Rules
#

This is the move that wins senior engineers over instead of displacing them. They stop reviewing code and start reviewing the gates: the security critic’s checklist, the complexity thresholds, the blast-radius classifier.

Their judgment now scales to every change instead of being spent once on a single pull request. A senior who owns the duplication rule has more leverage than a senior who reads ten diffs a day and remembers three. You are not removing them from the loop; you are promoting them to a loop where their work compounds. Give your skeptics the highest-leverage role in the new system, and most of them stop being skeptics.

Let Shadow Mode Persuade
#

The strongest argument is not yours; it is the comparison the team runs itself. Let a team keep their human review while the automated lane runs silently beside it, recording what it would have caught and what it would have flagged wrongly.

After a few weeks you have the only case that lands: “your reviewers missed these defects the gate caught; the gate’s false positives were these, and they are fixed.” Run it on a low-risk slice so the stakes match the trust level. And let the skeptics be the ones who present the results. Nothing converts a skeptic faster than being the person who found the data.

Measure What They Actually Care About
#

They are concerned about defects escaping, so measure defects escaping. Track defect escape rate, rollback rate, time-to-detect, and change failure rate, and compare the automated lane to the human lane on the same slice.

If the gate wins, the argument ends, and it ends in numbers rather than opinion. If the gate loses, you have a concrete gap to close, and you have earned the credibility to say so out loud. Never defend the system with “a human looked at it”; defend it with production outcomes. A human looking at it was always a proxy for those outcomes, and a weak one.

Keep The Escape Hatch, And Say So
#

Irreversible changes, trust-boundary changes, public API changes, and changes to the gating system itself still get a deliberate human read.

The message is never “you are out entirely.” It is “you are in where you add value, and out where you do not.” That framing is far easier to accept than abolition, because it is not abolition. You are reserving humans for the small, identifiable minority of changes where a careful read is still the best tool we have, and the rest of the pipeline moves at the speed the machines can sustain.

Rebuild The Learning Channel
#

The learning concern from earlier does not go away once the rollout succeeds. That loss is a genuine cost, and pretending otherwise breaks trust.

Replace the channel deliberately. Pair juniors with seniors on specifications, because that is where the judgment now lives. Rotate rule-owner duty, so learning to curate a check is itself the apprenticeship. Hold code-archaeology sessions that read well-chosen diffs as learning material, not as a gate. Move the learning off the critical path without deleting it, and name the loss plainly so people know you took it seriously.

The First Escape Is The Moment That Matters
#

When, not if, the gate lets something through, the instinct of the holdouts will be to say “see.” That is the moment the rollout is won or lost.

Run a blameless postmortem, find the gap, and add a rule. Then say the thing human review can never say: this exact defect class can never recur, because it is now checked on every change, for as long as the rule exists.

That guarantee is the property that dissolves resistance on its own schedule. The system gets strictly better over time; human review resets every morning. Each incident encoded into a rule is a permanent gain. Show people that compounding curve, and most of the holdouts come around, not because you argued them down, but because the system stopped being the thing they distrusted.

What to Do Next
#

Start with the commenting bot on a low-blast-radius lane, and add the blocking bot and then the approving bot weeks apart. Run shadow mode beside the human lane, and let the skeptics be the ones who present the comparison. From the first automated action, publish the rule, the threshold, the owner, and the numbers: defect escape rate, rollback rate, time-to-detect, change failure rate.

Keep the escape hatch explicit, and rebuild the learning channel deliberately. Reserve deliberate human reads for the changes with real blast radius. Pair juniors with seniors on specifications, and rotate rule-owner duty, so the apprenticeship continues off the critical path.

When the gate lets its first defect through, run a blameless postmortem and encode the fix as a rule. That rule is the permanent gain the rollout was for: checked on every change, for as long as the rule exists, while human review resets every morning.

See also
#


Verifying Code Without Reading It

It is a familiar observation by now that most code review happens without anyone reading the code (the case is made in You Already Review Code Without Reading It). That observation raises a question. If no human reads the diff, how do you still get correctness, maintainability, extensibility, and the rest of the things review was supposed to deliver?

The short answer is that you stop trying to read, and start trying to verify. Reading is one tool doing many jobs poorly. Verification is many tools, each doing one job well. Done right, the unread change is checked more thoroughly than the read one ever was.

Decompose What “Reading” Was Checking
#

When a reviewer reads code, they are not doing one thing. They are running a dozen checks in their head at once, and doing each of them inconsistently. Make those checks explicit, and a different picture appears.

Correctness: does the code do what the task actually asked for? Tested: are the behaviors that matter covered by tests that would fail if the code were wrong? Maintainable: is the complexity bounded, the duplication low, the naming consistent, the dead code absent? Extensible: can the next change plug in without a rewrite, or has this change coupled two things that should stay independent? Secure: does it cross a trust boundary, leak a secret, or open an injection path? Reversible: if this is wrong, can we undo it in minutes, or does it mutate data we cannot get back?

A tired reviewer scans for all of these at once and catches each of them sometimes. The move is to give each property its own check, run on every change, without getting tired.

Match Each Property To Its Best Checker
#

Every property above has a checker that is cheaper and more reliable than a human reading a diff.

Correctness is checked by tests, and by an LLM judge that compares the diff against the acceptance criteria from the issue and asks: is there a case where the criteria hold but this code fails? Tested is checked by coverage gates and by mutation testing, which mutates the code and fails the build if the tests still pass, because that means the tests were not testing anything. Maintainable is checked by complexity limits, duplication detectors, dead-code scans, and linters, all deterministic, all running every push. Extensible is checked by dependency-direction rules, boundary tests, and an LLM critic that asks where the next feature would plug in and whether this change has made that harder. Secure is checked by static analysis, secret scanning, and an adversarial LLM pass that tries to find the input the author did not think of. Reversible is checked by a blast-radius classifier: did this touch the schema, the public API, an external commitment, or a trust boundary?

None of these requires a human to read the code. Together they cover everything a reader was supposed to cover, and they cover it on every change, not on the changes a reviewer happened to be alert for.

The LLM As Critic, Not Reader
#

The LLM’s role in this system is not to read the code for you. A summary of the diff is not reading, however it is presented. The LLM’s role is to act as a critic against a single, stated property.

Give it the diff, the specification, and one question. Does this change introduce a coupling that violates the intended dependency direction? Is there an input that bypasses the authorization check the spec requires? Does this function do something the acceptance criteria never asked for? One question per critic, phrased so the answer is either a concrete failing case or a pass.

This is verification, not reading. It is adversarial by construction: the critic is rewarded for finding a problem, not for approving. And because each critic has one job, you can run many in parallel, each a different prompt, each looking for a different class of failure. A human reader tries to notice everything and notices some of it. A battery of critics tries to notice one thing each and notices it every time.

Verification Is Not Validation
#

There is a distinction worth holding onto, because the system fails if you blur it.

Verification asks: did we build the thing right, does the code meet its specification? Validation asks: did we build the right thing, does the specification solve the real problem?

Reading a diff does verification badly and validation not at all. No amount of staring at code tells you whether the feature should exist. That judgment has to live somewhere, and the productive place for it is upstream, in the specification, and downstream, in production.

So split the work. Put verification in the automated gates, where machines check the code against a precise spec on every change. Put validation upstream, where humans decide which problems are worth solving and write the acceptance criteria that encode that judgment. Put a third layer of validation downstream, in canaries, monitoring, and fast rollback, where production behavior is the ground truth no diff review can match.

The human does not read the code. The human writes what the code must satisfy, and then watches what the code does in production.

The Circularity Trap
#

There is one failure mode that will quietly ruin this system if you let it go unchecked. Do not let the same model, or the same prompt, both write the code and approve it.

When the same model acts as both author and verifier, the verifier inherits the author’s blind spots. A model that wrote a subtle bug will, asked to review its own work, tend to confirm that the work is fine. This is not malice; it is the same statistical process producing both answers.

The defense is structural. Use a different model for verification than for generation, or at minimum a different agent with a different prompt and different access to the specification. Write the verification criteria before the code exists, so they describe the intended behavior rather than rationalizing whatever was built. And run an adversarial pass whose only goal is to break the change, with no incentive to approve.

Separation of concerns is not a nicety here; it is the whole reason the system can be trusted.

What The Human Does Instead
#

If the human is not reading diffs, what are they doing?

They write the specification and its acceptance criteria, because that is where validation lives and where the LLM’s verification is anchored. They review the rules the gates enforce, not the code the gates pass, because a bad rule approved once produces bad approvals forever, while a bad line of code is caught by a good rule. They respond to gate failures, which is where their judgment adds value, instead of spending it on changes that passed cleanly. And they read code only on the small, flagged minority of changes that carry real blast radius, where a deliberate read is still the best tool we have.

The new division of labor is more work at the top of the pipeline and less at the bottom, which is the right inversion. You are trading a low-leverage activity that scaled poorly (reading every diff) for a high-leverage one that compounds (writing the rules and specs that check every diff).

How You Know It Is Working
#

The objection that will come is simple: how do you know the unread code is good enough?

You do not answer it by pointing at how much code you read. You answer it by measuring the outcomes reading was supposed to produce.

Track defect escape rate, the bugs that reach production per change. Track rollback rate, how often a merged change has to be undone. Track time-to-detect, how quickly a regression surfaces after it lands. Track change failure rate, the fraction of deployments that cause an outage.

Run the new system on a slice of changes and compare these numbers to your old, human-read pipeline. If the gates have a lower defect escape rate and a faster cycle time than your reviewers did, the unread code is provably better than the read code was. If they do not, you have a concrete gap to close, by tightening a rule or adding a critic, not by urging reviewers to read harder.

The standard is not “a human looked at it.” The standard is “the code behaves well in production, measurably, on every change.” A human looking at it was always a proxy for that standard, and a weak one. Replace the proxy with the thing it was standing in for.

See also
#


You Already Review Code Without Reading It

Most code review does not involve reading code. The decision to approve is made before the diff opens. We keep the ritual, and quietly drop the part that is supposed to justify it.

The same gap between claim and verification exists on the author’s side of the pull request. An LLM cannot vouch for its own output, because accepting a solution as done needs information the model does not have (The Acceptance Gap). The reviewer has the same problem in reverse: vouching for code that was never read.

What I Mean By Reading
#

There is a difference between opening a pull request and reading one. Reading means reconstructing what the code does, tracing each branch, checking every assumption against the rest of the system, and forming an independent opinion about whether it is correct. That takes time, and sustained focus, and the kind of attention that does not survive a fifteen-item review queue.

Opening is something else. You open the PR, your eye catches the file list, the green check from CI, the test count, the description the author wrote, and within a few seconds a feeling forms: this is fine, or this is not. The code itself sits downstream of all of that. Most of the time, you never reach it.

This is not a failure of discipline. It is how the tool presents work to you, and how a busy engineer responds to that presentation. The diff is the last tab you look at, and often the one you look at least.

The Signals That Actually Decide Approval
#

If you watch what an approver actually responds to, the code is rarely the first thing.

CI passed. That carries more weight than any line you might have read. The tests are green, the build is green, the linter is quiet, and that cluster of signals has already done the safety work before you arrived.

Then the author. A pull request from someone you trust gets a lighter read than one from a new hire. You will deny this in a meeting and do it anyway in practice. Trust is a real signal, and a useful one, but it is not “reading the code”.

Then the size of the change. A two-line diff to a README merges in seconds. A four-hundred-line refactor to the auth module stalls. You are reacting to blast radius, not to semantics, and you are right to, but again, that is not reading.

Then the description and the linked issue. These tell you what the author intended. For a lot of pull requests, the intent is all you ever verify, because the code is too long to verify against it in the time you have.

The code is the alibi. The signals are the verdict.

Why We Pretend To Read
#

If the code is not really what we are checking, why the ceremony around it?

Because reading code is the thing code review is supposed to be for. Admit that you are not doing it, and you admit that the central activity of the gate is not happening. That is an uncomfortable admission, so it does not get made. Instead we keep the form, the comments, the approving review, and let the substance slide.

There is also a blame function. When something breaks, we want to point at a name on the pull request and say “they signed off”. The signature has to mean “I read this and vouched for it”, or it cannot carry that blame. So we maintain the fiction that the signature means what it says, even though everyone, on both sides, knows it often does not.

This is accountability theater: the approve click certifies that a person was present, but rarely certifies that the code was read. The signature allocates blame after the fact. It does not prevent harm before it.

The Self-Test
#

Here is a test you can run on yourself. Approve your next five pull requests the way you normally would. The next day, try to recall a single line of code from each one.

If you cannot, be precise about what that means. It means you approved metadata. You were responding to CI, to the author, to the description, to the size of the diff, to your sense of the person. Those are signals, and some of them are good signals. But none of them are “I read this code and understood it”.

Run the same test on your team. Ask a recent approver to explain, from memory, one function that changed in the pull request they approved. The silence you get back is the most truthful data you will collect about how code review actually works in your organization.

I am not mocking reviewers here. I have failed this test more often than I have passed it. The point is that the gap between what review is supposed to be and what it is, is large, and we never measure it because measuring it would force a conclusion we have already decided not to reach.

Two Real Paths
#

Once you accept that most review does not involve reading, you have two defensible directions, and only two.

The first is to actually read the code, on the changes that warrant it. Not every change. The ones with real blast radius: irreversible changes, trust-boundary changes, changes to the gating system itself. On those, slow down, read deliberately, trace the logic, and form an independent opinion. This is high-leverage work, and it is rare, and it deserves the time it takes.

The second is to drop the pretense, and let the changes you were never going to read merge on the strength of the signals you were actually using. Green CI, a passing test suite, a trusted author, a small reversible diff. These already decided the outcome. Let them decide it without a human in the middle adding latency and taking credit.

What is not defensible is the current default. The default is the second path, with the theater of the first layered on top. You approve without reading, but you still require a human to approve without reading, and you call the combination a quality gate.

When The Code Was Written By A Machine
#

This was already true when humans wrote the code. It is sharper now that machines do.

When an LLM generates the diff, reading it line by line is even less useful, for reasons explored in Rethinking Code Review in the Age of LLMs. The reviewer becomes the only mind in the loop, reconstructing intent from output, and that reconstruction is harder and slower than reviewing a human who can at least be asked what they meant.

So the metadata review does not get more rigorous by being relabeled a code review. It gets more exhausting, and less effective, for the same rubber stamp at the end.

If you were not reading human-written code, you are not going to start reading machine-written code. You are going to keep approving signals, with a worse feeling about it. The productive move is to put the effort where it was always worth more: upstream, in the specification, the tests, and the gates.

What to Do Next
#

You do not need permission to start. Pick the low-blast-radius path and let it merge on green. Then be plain with your team about what the rest of the approvals were already doing.

When a change is large, irreversible, or crosses a trust boundary, read it for real. Budget the time. Treat it as the exception it is, not as the default presented as diligence.

And measure the gap. Ask your approvers what they remember from the pull requests they approved. The number will embarrass you, and that embarrassment is the beginning of a review process that is actually worth its cost.

See also
#


Issues Are Free Now: Send the Implementation, Not the Idea

For most of software’s history, filing an issue took real work. You had to reproduce the bug, narrow it down, write a clear description, and decide whether the request was worth the maintainer’s time. That filter is gone. An AI agent can read a README, a changelog, or spend ten minutes with the tool, and produce a polished, well-formatted issue in seconds, and it can do it a hundred times before lunch. The cost of creating an issue fell to almost nothing, the cost of clearing one did not, and every issue tracker on the internet is now overflowing.

If you are the one filing those issues, the result is hard to hear but clear. The issue you just opened is no longer a contribution in any real sense. It is a request for someone else’s work, and it is sitting in a queue next to nine hundred other requests for someone else’s work, most of them produced just as cheaply as yours. The only thing that still changes the outcome is the implementation attached to the request, because the implementation is the one input whose cost has not fallen.

The Issue Tracker Was Always a Wish List
#

It is worth being clear about what an issue tracker actually was, even before AI. A well-written bug report was a real gift, because it gave the maintainer a reproduction and a starting point. A feature request was almost always a wish. Someone else should do this. Someone else should want this. Someone else should spend their Saturday on the thing I would like to have.

Maintainers cleared the gifts and let the wishes pile up, and that was fine, because the wishes cost the filer enough effort to write that only the motivated ones made it through. The friction was the filter. The issue tracker was tolerable only because filing an issue was mildly annoying, and the mildly annoyed filer self-selected for “I actually care.”

Remove the friction and the wish list becomes the whole tracker. That is where we are.

The Cost Imbalance Is the Whole Problem
#

The math that kills the tracker is simple. It costs a filer roughly zero seconds and zero cents to open an issue with an AI agent. It costs a maintainer anywhere from five minutes to an afternoon to triage it: read the request, check whether it duplicates something, decide whether the project wants it, find the relevant code, and either close it, schedule it, or do it. One side of that exchange is free; the other side is unpaid, and the unpaid side is the one you are asking to work.

Multiply that imbalance by a few hundred issues a week and you get the current state of every popular open source repository on the planet. The triage queue grows faster than any single human can read it, the maintainer starts ignoring the tab, the contributor waits six months for a reply, and the project slowly gains a reputation for being unresponsive. Nobody is at fault. The system is just balanced so that the cheap side produces faster than the expensive side can consume, and the buffer between them is a person.

This is the same dynamic I described for pull requests in The Pull Request Queue Outgrew You: the cost to create a change fell below the cost to review it, and the maintainer became the bottleneck in their own queue. What is true for code changes is now true for ideas, and ideas are even cheaper to produce.

An Implementation Rewrites the Ask
#

Here is the move that fixes the imbalance, and it is the only move that fixes it. Stop sending the description. Send the code.

A pull request with a working implementation changes the maintainer’s job from “do this work” to “evaluate this work,” and those are not the same task. Doing the work is open-ended, expensive, and unpaid. Evaluating the work is limited, fast, and something a maintainer can actually fit into a Saturday morning. The difference between “you should add a dark mode” and “here is a dark mode, here are the tests, here is the screenshot, merge or reject” is the difference between a request for a favor and an offer to work together. The implementation is what converts your issue from a cost imposed on the maintainer into an option extended to them.

This is also why the maintainer is so much more likely to act on a pull request than on an issue. A PR gives them a simple choice they can make in minutes. An issue gives them an open-ended commitment that will eat an afternoon if they engage with it at all. If you wonder why your feature request has sat untouched for two years while a stranger’s twenty-line PR landed in a week, that is the entire explanation. The PR was cheap to say yes to. Your issue was expensive to say yes to, and expensive to say no to, so it got neither, which is what happens to expensive things in a full queue.

The Cost of Implementing Fell Too
#

The obvious pushback is that not everyone can write the implementation, and that used to be true. It is not true anymore.

The same AI agent that filed the issue can write the pull request. You describe what you want, the agent reads the codebase, produces a diff, writes the tests, and opens the PR. The skill floor for “I can send a working implementation” dropped from senior engineer to motivated user with a coding agent and an afternoon. The reason to keep sending descriptions instead of implementations is no longer that you cannot produce the implementation. It is that you have not updated your habits to match the new cost structure.

This is the same shift The Shifting Bottleneck keeps arriving at. Producing code is no longer the constraint. Deciding what to produce, and accepting what got produced, is the constraint. In an issue tracker, that means the scarce resource is no longer “someone willing to file the issue” or even “someone willing to write the code.” The scarce resource is the maintainer’s attention, and the only way to earn a slice of it is to bring the work to a state where saying yes costs them almost nothing.

But I Am Not Sure My Implementation Is Good
#

This is the second pushback, and it is more serious than the first. You worry that sending an AI-generated PR is dumping low-quality work on an already overloaded maintainer, and that is a real risk. The answer is not to hold back the PR. The answer is to do the work that makes the PR cheap to evaluate.

Run the tests. Write a new one for the behavior you added. Reproduce the bug you claim to fix, and show the test going red before your patch and green after. Keep the change small and focused on one thing. Link the issue you are closing. Write a PR description that lets the maintainer evaluate the change in thirty seconds. Every one of these is an attempt to lower the cost of saying yes, and lowering the cost of saying yes is the entire game.

A small, tested, well-described PR is a gift. A large, untested, AI-generated diff with a one-line description is just a different kind of issue, and it will be treated like one. The discipline is not “send code instead of an issue.” The discipline is “send code that is cheaper to merge than to discuss, and if you cannot produce that, the issue tracker will not save you either.”

What the Maintainer Actually Wants
#

It helps to look at the exchange from the maintainer’s chair for a moment, because the request they are implicitly making is not unreasonable. They want to spend their scarce attention on decisions only they can make: the architecture, the direction, the boundary between what the project is and is not. They do not want to spend it turning a feature description into code, because that is the part the LLM can do now. The highest-value contribution you can make is one that arrives with every reversible decision already made and every test already green, so that the maintainer’s job is reduced to a judgment call they were going to have to make anyway.

This is the open source version of the argument in Rethinking Code Review in the Age of LLMs. When the implementation is cheap to produce, the value concentrates upstream, at the point where someone decides what the implementation should be. A good contributor in this era is not someone who writes clean code. A good contributor is someone who arrives with a concrete proposal, already implemented, already tested, already de-risked, leaving the maintainer nothing to do but approve, redirect, or reject.

What About Bugs You Cannot Reproduce
#

There is a real exception, and it should be named so the argument does not go too far. Some issues are genuine bug reports where the value is in the reproduction, not in the fix. A crash that happens on a specific kernel version, a regression that only shows up under load, a data corruption bug that requires a particular sequence of inputs. These are gifts, when written carefully, because the maintainer could not have produced the reproduction themselves.

But notice what makes them gifts. They carry information the maintainer did not have and could not get cheaply. The test for whether your issue is a contribution or noise is exactly this: does it contain information the maintainer could not have produced on their own in fifteen minutes with an AI agent? If yes, file it. If no, the issue is a request for someone else to do work you could have done yourself, and the queue is full of those already.

The same test separates a useful feature request from noise. “Add dark mode” is noise. “Add dark mode, here is the design, here is why it does not conflict with the theming system, here is the PR, here are the screenshots, here are the tests” is a contribution. The information that converts noise into contribution is exactly the information that comes from having tried to build the thing.

What to Do Next
#

If you file issues against projects you depend on, change one habit. Before you open the issue, try to open the PR instead. Point an agent at the codebase, describe the change you want, and let it produce a draft. Spend the time you would have spent polishing the issue description on getting the PR to a state where merging it is the obvious move: tests passing, scope small, description clear. File the issue only if the PR attempt really failed, and when you do, include what you learned from the attempt, because that is the information the maintainer actually needs.

If you maintain a project, change the default you invite. Rewrite your contributing guide to say, plainly, that feature requests without an accompanying PR will be closed, and that bug reports without a reproduction will be closed faster. Be clear that this is not hostility. It is the only way the queue stays manageable. Make the pull request template the front door, and the issue template the side door for the narrow set of things only an issue can carry. Raising the floor on contributions is, in this era, an act of respect for the contributors who are willing to meet it, because they are the ones whose work will otherwise be buried under the noise.

And for everyone, learn the new cost structure. Ideas are free. Descriptions are free. Issues are free. Implementations are the only currency left that buys maintainer attention, and the reason is not that maintainers are picky. It is that implementations are the one input whose cost has not fallen to zero, which makes them the one input that still signals you meant it. If you want a maintainer to take your request seriously, prove it the only way that still costs you something: by sending the code.

See also
#

  • The Pull Request Queue Outgrew You - the companion argument from the maintainer’s side: the cost to create a change fell below the cost to review it, and triage has to replace review
  • The Shifting Bottleneck - the upstream pattern: producing code is no longer the constraint, deciding and accepting are, which relocates where contribution lives
  • Rethinking Code Review in the Age of LLMs - why value concentrates upstream at the decision point when implementation becomes cheap
  • The Acceptance Gap - the maintainer’s remaining job is acceptance, and an implementation is what lets them do that job in minutes instead of afternoons
  • Feature Parity Is Not a Moat - the parallel collapse of copy-time, which made features cheap to clone the same way AI made issues cheap to file

Who Resolves the Merge Conflict? Why the Bot and the Author Are Not Interchangeable

A pull request falls behind main and conflicts. Who resolves the conflict, the bot or the author? The answer depends on whether the conflict is mechanical or semantic, and treating the two as the same job is what makes every flat policy, “the bot rebases everything” or “the author handles it,” wrong.

The clean rebase is settled and out of scope; automate it on every push to main. The live question is the conflict, and the conflict is not one thing. Resolving a conflict means deciding what the code should now say, and that decision is a claim about what the author meant. Someone has to make it, and the interesting question is who can back it with intent they actually hold. The choice depends entirely on whether the conflict has a unique correct answer an oracle can check, or whether its correctness lives only in the author’s head.

Why the Question Got Loud
#

For most of git’s history the conflict question was quiet. A contributor resolved their own conflicts, or the maintainer did, and the round trip was short because the people involved shared a mental model of the code. Two things broke that equilibrium.

The first is auto-merge. Once low-risk changes land on green without a human clicking merge, a PR that cannot merge cleanly becomes the thing that stalls the whole lane. A human can absorb a conflicted PR by glancing and clicking; an automated merge pipeline cannot. Auto-merge makes conflict resolution a prerequisite of the lane, and that turns “who resolves” from a courtesy into a structural question.

The second is the model-authored pull request. When the author is a model, the conflict round trip behaves differently at both ends. The bot never sleeps, so a resolve request adds no latency the way it does for a human. But the bot also has no private knowledge of what the code was meant to do, so its resolution is a guess about intent presented as a fix. This is the plausibility problem, and it lands hardest exactly where the conflict is hardest.

So the modern repository has a lane that wants every PR mergeable on green, and a growing share of PRs whose intent lives only in the code a model produced, with no description, linked issue, or spec that states it independently. When the intent is encoded somewhere, a conflict has an oracle to check a resolution against. When it is not, there is nothing to verify the resolution by, and that, not the absence of an author to interrogate, is the actual problem. The old default, the contributor resolves, is too slow for the merge lane, and the bot is too confident to stand in as the oracle, so the live question is not who authored the PR but whether the intent exists outside someone’s head.

The Conflict Has a Gradient
#

Conflicts are not all the same, and the gradient is what lets you route them accurately.

At one end is the mechanical conflict: an import added in both branches, a trailing comma, two functions inserted at adjacent positions, a formatting drift the formatter can settle. Resolve it and there is exactly one answer any reasonable developer would accept, and a test suite that passes before will pass after. At the other end is the semantic conflict: two people changed the same logic for different reasons, and the correct resolution depends on which reason was right. The mechanical conflict has a unique correct answer that a test can falsify; the semantic conflict has no correct answer without the intent that motivated the change.

The mechanical conflict is therefore still logistics, just logistics that needs an oracle. Resolve it, run the suite, and if the suite passes the resolution is, by construction, the one the project already trusted. Here the test acts as the independent oracle the model cannot be, the same role it plays in closing the bug gap when a fix claims to be done. A conflict whose wrongness a test can catch is a conflict the bot may resolve, because the test, not the model, is signing off.

The semantic conflict is a different kind of problem. Nothing external is left to disagree with the model’s plausible stitch, because the definition of “correct” is the intent, and the intent is precisely what the model does not have. A bot that resolves a semantic conflict is making an intent claim it cannot source, and the harder the disagreement, the more likely the claim is a confident fabrication.

Where the Intent Lives
#

The semantic conflict splits once more, and it splits on a cleaner axis than who wrote the PR: whether the intent is encoded anywhere a resolver can read it.

Intent can live in two places. It can be encoded, written down in the PR description, a linked issue, an acceptance criterion, a failing test, anything a human authored to state what the change was supposed to do. Or it can be tacit, held in someone’s head and never written down, surfacing only if you ask. A conflict is safe for the bot to resolve when the intent is encoded and the encoded source covers the disputed region, because then the resolution has an oracle. It is not safe when the intent is tacit, because then nothing external can falsify the bot’s stitch, and the stitch will look right whether or not it is.

Author type matters, but only as a proxy for where the intent lives, and the proxy is worth stating because it inverts the naive assumption.

When the PR is human-authored, the human’s own description is an independent statement of intent. If the description covers the conflict, the bot may resolve against it, and the description, not the model, is the oracle. If the description is thin or silent on the disputed region, the intent is effectively tacit, and the only defensible move is to ask the human who holds it.

When the PR is bot-authored, the trap is that the bot’s description is not an independent oracle. It was written by the same model that wrote the code, or a sibling of it, so checking the resolution against the description is checking the model against its own narration, which provides no real verification. The only trustworthy intent source for a bot-authored PR is something a human wrote upstream: the issue, the spec, the prompt, an acceptance test. When that upstream source exists and covers the conflict, the bot may re-derive against it, and the re-derivation is checkable. When it does not exist, the intent was never encoded by anyone, and the bot’s resolution is a guess about intent no one ever wrote down.

This is the refinement the flat “maintainer picks a default” framing misses. A single repository-wide default is too coarse, because the right default is a function of whether the PR carries an encoded, independent intent source, and that varies PR by PR, not repository by repository. The PR that links a human-authored issue with acceptance criteria is safe for bot resolution regardless of who wrote the code; the PR that arrives as code and a self-description is not safe regardless of who wrote the code.

The Label Is the Right Mechanism, the Default Is the Question
#

Given the split, the mechanism worth reaching for is the one the question already points at: a label on the PR that decides who resolves a conflict. Labels are already how a modern triage layer expresses every other routing decision, and conflict resolution is no different in kind. The label should express the thing that actually varies, which is not “is this PR rebased” but “who is allowed to resolve a semantic conflict on it.”

For a mechanical conflict there is no decision worth encoding: the bot resolves, the test gates, and a conflict: auto-resolved note is enough audit trail. The label is worth having at the semantic tier. Something like conflict: author-resolves versus conflict: bot-may-resolve, defaulted per repository by the maintainers and overridable per PR, is the right form, and it is the form the question proposes.

The interesting work is in the default, and the sound default routes on intent availability, not on author type. A PR that links a human-authored issue or spec, with acceptance criteria that cover the change, defaults to bot-may-resolve, because the resolution has an oracle. A PR that arrives as code alone, with no independent statement of intent, defaults to author-resolves, because nothing can falsify the bot’s stitch. The maintainer’s real choice is not “auto or manual.” It is “what do we assume about a PR whose intent source we cannot verify.” The conservative answer, treat the PR as unencoded and ask the author, costs a little latency where the bot could have handled it. The permissive answer, resolve and trust the model, costs intent claims that are silently wrong. Between losing a little speed and shipping a confidently wrong merge, the speed is the cheaper loss, so the default for a PR with no verifiable intent source should be author-resolves.

This makes the highest-leverage change a documentation change, not a tooling one. Require, in the pull request template, a linked issue or a short intent statement that covers the change, and ask whether the PR was model-assisted. The linked issue turns an unencoded PR into an encoded one; the model-assisted flag tells you whether the PR’s own description can serve as an oracle or whether you need the upstream source. Both shrink the “no verifiable intent” bucket, and shrinking that bucket is what lets the auto-merge lane actually run.

What to Do Next
#

First, surface the conflict instead of hiding it. Add eps1lon/actions-label-merge-conflict so any PR that falls behind main gets a merge-conflict label the moment it conflicts, and loses it the moment it merges again. Your auto-merge lane can now filter -label:merge-conflict and only act on work that is actually ready. The author gets an automated nudge that a real decision is needed, without you being the one to say it.

Second, classify the conflict before you route it. Mechanical conflicts (imports, formatting, adjacent non-overlapping edits) go to the bot behind the test gate, and the test is the oracle that makes the resolution trustworthy. If the suite fails, the resolution is wrong by definition and the bot escalates rather than ships.

Third, route the semantic conflict by whether an independent intent source exists. If the PR links a human-authored issue or spec that covers the disputed region, let the bot resolve and check the result against that source, with an encoded test that must pass on the resolved tree. If the PR carries no such source, post the conflict as a comment and drop the PR to author-resolves until someone provides the intent or resolves it by hand.

Fourth, choose the conservative default for the case where you cannot tell. When a PR arrives with no linked issue and no intent statement, default to author-resolves, because the cost of a confidently wrong merge is higher than the cost of a round trip, and the round trip at least asks the question the silent resolution skips.

Fifth, make the routing signal cheap to produce. Require a linked issue or a short intent statement in the pull request template, and ask whether the PR was model-assisted. The link is what turns an unencoded PR into an encoded one, and the model-assisted flag is what tells you whether the PR’s own description can count as the oracle.

Do not write a policy that says “the bot resolves everything” or “the author resolves everything.” Both are the original mistake in policy form. Gate the mechanical conflict behind a test, defer the semantic conflict to an independent intent source when one exists, and defer to the author when one does not.

See also
#

  • Triaging Open Source Pull Requests - the upstream layer this refines: labeling merge conflicts and routing by risk is the triage move, and conflict-tier routing is what makes the auto-merge lane inside that layer safe
  • The Merge Gate - the case for gating on the properties of a change rather than on the existence of a PR, which is the same principle applied here to “is this a mechanical conflict or a semantic one”
  • The Acceptance Gap: Why an LLM Solution Is Not a Shipped Solution - why a conflict resolution is a mini-acceptance gap: the mechanical tier wants a check (verification), and the semantic tier wants the author’s reaction (validation)
  • Rethinking Code Review in the Age of LLMs - the plausibility problem in full, the reason a bot resolving a semantic conflict is making a claim about intent it cannot source