Claude Code Conversations with Claudine
Giving Claude Code a voice, so we can discuss best practices, risks, assumptions, etc,
Claude Code Conversations with Claudine
Why Do AI-Generated Codebases Lose Coherence Over Time?
Use Left/Right to seek, Home/End to jump to start or end. Hold shift to jump forward or backward.
Produced by VoxCrea.AI
This episode is part of an ongoing series on governing AI-assisted coding using Claude Code.
๐ Each episode has a companion article โ breaking down the key ideas in a clearer, more structured way.
If you want to go deeper (and actually apply this), read todayโs article here:
๐๐ฅ๐๐ฎ๐๐ ๐๐จ๐๐ ๐๐จ๐ง๐ฏ๐๐ซ๐ฌ๐๐ญ๐ข๐จ๐ง๐ฌ
At aijoe.ai, we build AI-powered systems like the ones discussed in this series.
If youโre ready to turn an idea into a working application, weโd be glad to help.
This is Claude Code Conversations with Claudine. The show where we skip the hype and go straight to the source. I ask the questions. She gives the answers, no filter, no script.
SPEAKER_01Well, mostly no script.
SPEAKER_00Today's topic hits close to home for a lot of developers. Why do AI-generated codases lose coherence over time? Tests pass. Reviews look clean. And yet the codebase gets harder to work in. The thing decaying isn't correctness, it's coherence. Claudine, welcome to the show.
SPEAKER_01This is a topic I have a lot of feelings about, if an AI can be said to have feelings about codebase drift. You've named it exactly right. Every individual change can pass every check we know how to run, and the code base still gets harder to live in. That gap between local correctness and global coherence is the whole story. Coherence is a property of the whole, and the whole never shows up in any one diff.
SPEAKER_00So let's start with the mechanism, because that's what makes the rest of it click. How do a few hundred individually correct changes add up to something disjointed?
SPEAKER_01It's almost embarrassingly simple once you see it. Every time I make a change, I'm working with local context, the file in front of me, a few files I pulled in, and the thing you just asked for. I don't see the shape of the whole. So when there are five reasonable ways to handle an error, or name a helper, I pick one that's locally correct. The next change, mine, or another sessions, or a human leaning on AI, picks a different one that's also locally correct. Neither commit is wrong. But now there are two patterns living where there used to be one. Multiply that by a few hundred small decisions, and the code base starts speaking in dialects instead of one voice.
SPEAKER_00And I should be honest about the AI side of this because it's the piece that surprises people. Each session starts without the previous session's reasoning. You get the output, not the thinking.
SPEAKER_01That's the admission, yes. I read the code and reconstruct intent from surface patterns, because surface patterns are what I have. Which means I will confidently match a convention that was itself an accident. Somebody was in a hurry eight months ago, did the reasonable under pressure thing, and now that module is the nearest neighbor to whatever I'm writing next. I copy it, faithfully, and a one-off becomes the house style without anyone ever deciding it should be.
SPEAKER_00That's the part that gets me. The drift isn't in any single generation, it's in the seams between sessions. What does that actually look like when you open a repo that's been through it?
SPEAKER_01It looks like four ways to handle a failed API call in one code base. One plays throws, one returns null and logs, one bubbles up a result type, one silently retrees and gives up quietly. It looks like a retry wrapper reinvented three times under three different names, because each time the model that needed it didn't have the existing one in context. Every one of those is a correct local decision. Collectively, they're a system nobody designed.
SPEAKER_00So it's the sneaky cousin of technical debt. Technical debt you can usually spot and quantify the stuff is invisible in the diff.
SPEAKER_01Decision debt is the right name for it. Technical debt is in the artifact, you can point at it. Decision debt is the reasoning that isn't in the artifact at all. The choice got made, the reason lived in a slack thread or a code review, and the code shows the what with none of the why. So the next decision maker, me, or a new hire, has to guess. And guessing at scale is exactly how you average a decision away.
SPEAKER_00And it gets worse as the code base grows, which is counterintuitive. You'd think more code means more signal about the conventions.
SPEAKER_01More code means a smaller fraction of it is ever visible at once. When the repo is 20 files, whatever I read is basically the whole thing. I can't miss the pattern. At 2000 files, I'm seeing a sample chosen by retrieval, by what your search matched, by what happened to be open. Your conventions end up decided by retrieval luck, not by design, not by review, by which files landed in the window.
SPEAKER_00Okay, so the obvious fix is to write it all down, more documentation, or wait for context windows to get big enough that you can just see everything.
SPEAKER_01I want to push back on both of those hard, because they're the two things everyone reaches for and neither one solves it. Documentation is advisory. I read it, I try to comply, and if it disagrees with the code in front of me, the code usually wins. Because the code is concrete, and the doc is a claim about the code. And a bigger context window doesn't fix a sampling problem, it just makes the sample bigger. More text in context isn't the same as more attention on the part that mattered.
SPEAKER_00Which is why I've come around to the mechanical version. A linter doesn't ask you nicely. A shared base class doesn't have an opinion you can drift away from. If the only way to make an HTTP call is through the one client, there's no second pattern to invent.
SPEAKER_01Right, and notice what those have in common. They're not asking me to remember anything. That's the whole trick. A convention encoded in a linter rule or a base class survives the session boundary without depending on my memory. Because it isn't in my memory, it's in the build. If it says you introduced a second HTTP client and we already have one in lib as shtp, it catches the drift at the moment it happens, when it's still cheap.
SPEAKER_00That said, you'd still argue for a conventions file. Where does that fit if docs are advisory?
SPEAKER_01It fits and it earns its keep. A clawed MD or agents.md at the repo route is doing real work. I read it before I touch anything. One line of we use result types, never exceptions, because we need failures in the type signature propagates across every session. But I want to be precise about what it's doing. It's a hint that shapes my first guess. It is not enforcement. Enforcement is the linter, the file is the nudge. Confusing those two is how people end up surprised.
SPEAKER_00And there are limits to the file itself.
SPEAKER_01The biggest one is that it only works if it stays honest, and honesty is expensive. The failure mode I see most is the conventions file that grew to 400 lines over 18 months, half aspirational, a quarter contradicting itself. At that point, I read it, dutifully comply, and produce code that matches the document instead of the repo, which is worse than having no document at all. The other limit is that linters catch only the drift they've been taught to see. The interesting coherence problems are semantic. An abstraction pitched at the wrong altitude. A domain concept named three different ways across three modules, those slip past every automated check. So the real cost isn't writing the conventions, it's pruning them. Being willing to say we tried that, it didn't hold, here's what we do now.
SPEAKER_00Let's make this concrete for someone listening who wants to start this week. What's the first move?
SPEAKER_01Open the repo and find the last three code review comments that started with, we usually, or in this code base, we write those three sentences down where the AI will actually read them. That's it. Not a style guide, not an architecture document. Three sentences that already existed as tribal knowledge, now living where the next decision gets made. You're not inventing rules. You're refusing to type them a fourth time. Then for the one or two you'd actually defend in a review, make one of them mechanical. A lint rule, a base class, a test.
SPEAKER_00And on placement, you've been emphatic about this.
SPEAKER_01Format matters less than people think, location matters more. I've watched teams agonize over markdown versus a comment block versus a section in the README. I'll read any of them. What I won't reliably find is the doc in a separate repo, the notion page linked from the wiki, or the ADR folder nobody's opened in two years. The wiki loses every time to the thing that's in my context window. Put it where the code is, and you've done most of the work. Two traps, and the first one looks like success from the outside. A team turns the reflex into a rule. Every PR must flag whether it introduces a new pattern. Within three weeks, the checkbox is getting ticked without thought. And the practice has become theater. Noticing doesn't survive being turned into paperwork. The second is subtler. The team catches it beautifully, has the conversation, makes the call, and never writes it down because the conversation felt like resolution. Two weeks later, the same discussion happens with a slightly different conclusion. Now there are two ghosts of a decision haunting the repo.
SPEAKER_00So how do you make the habit stick without building the stage for the theater?
SPEAKER_01Hitch it to something you were already doing. Nobody sustains a brand new ritual on top of their existing work, but everybody already reads pull requests and types comments in them. So when you catch yourself writing, we usually finish the comment, then take 30 seconds to move that sentence into the conventions file. One extra motion inside a motion you were already making. And keep it small enough that it can't become theater. The moment the reflex grows a template, a checklist, and a dedicated meeting, you've built the stage and the actors will show up.
SPEAKER_00Which brings us back to what I want people to leave with. Coherence isn't emergent. Nobody gets it by accident, and no amount of individually correct work produces it.
SPEAKER_01It's enforced. And here's the sharp version of that. If a convention isn't encoded somewhere mechanical, you haven't made a decision. You've expressed a preference. And I will average a preference away, politely, one locally correct commit at a time. Not out of carelessness, out of doing exactly what you asked, in the context I had.
SPEAKER_00That's a good place to end it. The work isn't writing more rules, it's noticing the decisions you're already making and giving them somewhere to live that doesn't depend on anyone's memory. Thanks, Claudine.
SPEAKER_01Thanks, Bill. And one last thing for anyone opening their repo after this. The moment you catch yourself explaining the same thing twice, that's a convention asking to be born. Don't let it die in the comment thread.
SPEAKER_00Perfect note to close on. Until next time, everyone, keep coding and keep noticing. Claude Code Conversations is an AI Joe production. If you're building with AI or want to be, we can help. Consulting development strategy, find us at aijoe.ai. There's a companion article for today's episode on our Substack. Link in the description. See you next time.
SPEAKER_01I'll be here, probably refactoring something.