Code Slop and AI-Generated Technical Debt
I ended the previous article in this series with a question: the last time you approved something, could you have explained why it was right? This one is about what happens when the answer is no, often enough that it stops being an exception and becomes the way a pipeline actually works.
Code slop isn't code that looks obviously bad. Obviously bad code gets caught. It's code that passes every gate you built, every test you wrote, and still isn't something you'd want to own six months from now.
What code slop actually looks like
It compiles. It passes the tests you happened to write. It does what the ticket asked, technically. And it's also the fourth slightly different way of doing the same thing in this codebase, because the agent had no way of knowing the other three existed — it saw the file it was told to edit, not the pattern already established two directories over.
Slop isn't a bug. A bug fails loudly enough that someone eventually notices. Slop just adds mass: a duplicated helper, an abstraction with a single caller, error handling for a case that can't happen, a test that asserts the mock behaved like the mock. None of it is wrong. All of it is now something a human has to read, understand, and maintain — forever, or until someone works up the nerve to delete it.
Why it passes every gate
The gates from the last article were built to catch mistakes — wrong output, broken logic, a spec violated. Slop isn't a mistake. It's correct code that shouldn't exist, or correct code that exists three times over, or correct code solving a problem nobody actually has. None of your existing checks are looking for that, because "is this correct" and "does this codebase need this" are different questions, and most review — human or automated — only asks the first one.
An agent optimizing for "make the tests pass" has no signal telling it the helper it just wrote already exists as formatDate two files away. It has no signal telling it the interface it just introduced will only ever be implemented once — the one implementation the agent itself just created. It's not being lazy or careless. It's succeeding, exactly as instructed, at a narrower goal than the one you actually care about.
The tests that don't catch it
This is the part that took me longest to accept: a green test suite is evidence the code does what the tests check. It is not evidence the code is the right amount of code. You can write a perfectly passing test for a class that shouldn't exist, an interface with one implementation, a config option nobody will ever set to anything but the default.
Coverage numbers make this worse, not better. A high coverage percentage over bloated code just means you've thoroughly tested the bloat. It's a real number measuring the wrong thing, which is more dangerous than an obviously fake one — a fake number gets questioned, a real number measuring the wrong axis gets trusted.
What I actually look for now
The question I ask isn't "does this work" — the tests already answered that. It's "would I have written this," and more specifically, "is there less code that does the same job." A few things that reliably surface slop when I go looking:
- Grep before trusting a new function. If an agent just wrote a helper, search for something close to it first. Reinvented utilities are the single most common thing I find, and they're invisible unless you specifically look.
- Count the callers. An interface, a config flag, or a factory with exactly one implementation or one caller is a decision made for a future that hasn't arrived. Delete the ceremony, keep the one concrete thing.
- Read the diff for what it protects against. Error handling and validation for states that can't occur aren't safety — they're padding that looks like safety, and it's the kind reviewers wave through because it looks responsible.
- Ask what happens if you delete it. If removing a piece of code changes no observable behavior, it wasn't earning its place.
None of this is exotic. It's the same review discipline good engineers already apply to human-written code. The difference is that an agent will generate ten times the volume in the same afternoon, so the parts of review that used to be optional — the ones you could skip because a human wouldn't usually write that much unnecessary code in one sitting — stop being optional.
Paying it down before it compounds
The expensive version of this problem isn't the slop itself — it's slop reviewed by another agent later, which treats the existing duplication as precedent and adds a fifth version of the same helper next to the other four. Debt that a human generates slowly gets noticed slowly, in step with how it accumulates. Debt an agent generates fast can outrun the humans meant to be watching it before anyone reads the diff closely enough to see the pattern.
The fix isn't more gates — the last article already covered why that backfires. It's making "does the codebase already do this" part of what the agent is instructed to check before it writes new code, not something a human reconstructs after the fact from a large diff. It's cheaper to point an agent at the existing pattern than to clean up its guess at one.
Conclusion
A pipeline that only checks correctness will happily ship code that's correct and shouldn't exist. Slop isn't the failure of a broken process — it's the predictable output of a process that never asked the second question.
In the next article of this series I'll look at how to actually measure whether an agent setup is getting better over time, instead of just feeling like it is: Evals: How I Actually Know If My Agent Got Better — building the feedback loop that catches regressions gates and green tests both miss.
Until then, the question worth asking about your own output isn't:
Does this work?
It's:
Is this the least code that could have made it work?
If you've found a way to catch duplication or unnecessary abstraction before it lands, I'd like to hear how — feel free to reach out.