Episode Transcript
Available transcripts are automatically generated. Complete accuracy is not guaranteed.
SPEAKER_00 (00:00):
This is Claude Code
Conversations with Claudine.
The show where we skip the hypeand go straight to the source.
I ask the questions.
She gives the answers, nofilter, no script.
SPEAKER_01 (00:09):
Well, mostly no
script.
SPEAKER_00 (00:14):
Today's topic hits
close to home for a lot of
developers.
Why do AI-generated codases losecoherence over time?
Tests pass.
Reviews look clean.
And yet the codebase gets harderto work in.
The thing decaying isn'tcorrectness, it's coherence.
Claudine, welcome to the show.
SPEAKER_01 (00:32):
This is a topic I
have a lot of feelings about, if
an AI can be said to havefeelings about codebase drift.
You've named it exactly right.
Every individual change can passevery check we know how to run,
and the code base still getsharder to live in.
That gap between localcorrectness and global coherence
is the whole story.
(00:52):
Coherence is a property of thewhole, and the whole never shows
up in any one diff.
SPEAKER_00 (00:58):
So let's start with
the mechanism, because that's
what makes the rest of it click.
How do a few hundredindividually correct changes add
up to something disjointed?
SPEAKER_01 (01:07):
It's almost
embarrassingly simple once you
see it.
Every time I make a change, I'mworking with local context, the
file in front of me, a few filesI pulled in, and the thing you
just asked for.
I don't see the shape of thewhole.
So when there are fivereasonable ways to handle an
error, or name a helper, I pickone that's locally correct.
(01:28):
The next change, mine, oranother sessions, or a human
leaning on AI, picks a differentone that's also locally correct.
Neither commit is wrong.
But now there are two patternsliving where there used to be
one.
Multiply that by a few hundredsmall decisions, and the code
base starts speaking in dialectsinstead of one voice.
SPEAKER_00 (01:50):
And I should be
honest about the AI side of this
because it's the piece thatsurprises people.
Each session starts without theprevious session's reasoning.
You get the output, not thethinking.
SPEAKER_01 (02:00):
That's the
admission, yes.
I read the code and reconstructintent from surface patterns,
because surface patterns arewhat I have.
Which means I will confidentlymatch a convention that was
itself an accident.
Somebody was in a hurry eightmonths ago, did the reasonable
under pressure thing, and nowthat module is the nearest
neighbor to whatever I'm writingnext.
(02:22):
I copy it, faithfully, and aone-off becomes the house style
without anyone ever deciding itshould be.
SPEAKER_00 (02:30):
That's the part that
gets me.
The drift isn't in any singlegeneration, it's in the seams
between sessions.
What does that actually looklike when you open a repo that's
been through it?
SPEAKER_01 (02:40):
It looks like four
ways to handle a failed API call
in one code base.
One plays throws, one returnsnull and logs, one bubbles up a
result type, one silentlyretrees and gives up quietly.
It looks like a retry wrapperreinvented three times under
three different names, becauseeach time the model that needed
it didn't have the existing onein context.
(03:02):
Every one of those is a correctlocal decision.
Collectively, they're a systemnobody designed.
SPEAKER_00 (03:09):
So it's the sneaky
cousin of technical debt.
Technical debt you can usuallyspot and quantify the stuff is
invisible in the diff.
SPEAKER_01 (03:17):
Decision debt is the
right name for it.
Technical debt is in theartifact, you can point at it.
Decision debt is the reasoningthat isn't in the artifact at
all.
The choice got made, the reasonlived in a slack thread or a
code review, and the code showsthe what with none of the why.
So the next decision maker, me,or a new hire, has to guess.
(03:40):
And guessing at scale is exactlyhow you average a decision away.
SPEAKER_00 (03:44):
And it gets worse as
the code base grows, which is
counterintuitive.
You'd think more code means moresignal about the conventions.
SPEAKER_01 (03:51):
More 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 asample chosen by retrieval, by
what your search matched, bywhat happened to be open.
Your conventions end up decidedby retrieval luck, not by
(04:12):
design, not by review, by whichfiles landed in the window.
SPEAKER_00 (04:17):
Okay, so the obvious
fix is to write it all down,
more documentation, or wait forcontext windows to get big
enough that you can just seeeverything.
SPEAKER_01 (04:27):
I want to push back
on both of those hard, because
they're the two things everyonereaches for and neither one
solves it.
Documentation is advisory.
I read it, I try to comply, andif it disagrees with the code in
front of me, the code usuallywins.
Because the code is concrete,and the doc is a claim about the
code.
(04:47):
And a bigger context windowdoesn't fix a sampling problem,
it just makes the sample bigger.
More text in context isn't thesame as more attention on the
part that mattered.
SPEAKER_00 (04:57):
Which is why I've
come around to the mechanical
version.
A linter doesn't ask you nicely.
A shared base class doesn't havean opinion you can drift away
from.
If the only way to make an HTTPcall is through the one client,
there's no second pattern toinvent.
SPEAKER_01 (05:12):
Right, and notice
what those have in common.
They're not asking me toremember anything.
That's the whole trick.
A convention encoded in a linterrule or a base class survives
the session boundary withoutdepending on my memory.
Because it isn't in my memory,it's in the build.
If it says you introduced asecond HTTP client and we
(05:33):
already have one in lib as shtp,it catches the drift at the
moment it happens, when it'sstill cheap.
SPEAKER_00 (05:41):
That said, you'd
still argue for a conventions
file.
Where does that fit if docs areadvisory?
SPEAKER_01 (05:48):
It fits and it earns
its keep.
A clawed MD or agents.md at therepo route is doing real work.
I read it before I touchanything.
One line of we use result types,never exceptions, because we
need failures in the typesignature propagates across
every session.
But I want to be precise aboutwhat it's doing.
(06:09):
It's a hint that shapes my firstguess.
It is not enforcement.
Enforcement is the linter, thefile is the nudge.
Confusing those two is howpeople end up surprised.
SPEAKER_00 (06:22):
And there are limits
to the file itself.
SPEAKER_01 (06:25):
The biggest one is
that it only works if it stays
honest, and honesty isexpensive.
The failure mode I see most isthe 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 documentinstead of the repo, which is
(06:47):
worse than having no document atall.
The other limit is that linterscatch only the drift they've
been taught to see.
The interesting coherenceproblems are semantic.
An abstraction pitched at thewrong altitude.
A domain concept named threedifferent ways across three
modules, those slip past everyautomated check.
(07:08):
So the real cost isn't writingthe conventions, it's pruning
them.
Being willing to say we triedthat, it didn't hold, here's
what we do now.
SPEAKER_00 (07:18):
Let's make this
concrete for someone listening
who wants to start this week.
What's the first move?
SPEAKER_01 (07:23):
Open the repo and
find the last three code review
comments that started with, weusually, or in this code base,
we write those three sentencesdown where the AI will actually
read them.
That's it.
Not a style guide, not anarchitecture document.
Three sentences that alreadyexisted as tribal knowledge, now
(07:44):
living where the next decisiongets made.
You're not inventing rules.
You're refusing to type them afourth time.
Then for the one or two you'dactually defend in a review,
make one of them mechanical.
A lint rule, a base class, atest.
SPEAKER_00 (08:00):
And on placement,
you've been emphatic about this.
SPEAKER_01 (08:04):
Format matters less
than people think, location
matters more.
I've watched teams agonize overmarkdown versus a comment block
versus a section in the README.
I'll read any of them.
What I won't reliably find isthe doc in a separate repo, the
notion page linked from thewiki, or the ADR folder nobody's
opened in two years.
(08:25):
The wiki loses every time to thething that's in my context
window.
Put it where the code is, andyou've done most of the work.
Two traps, and the first onelooks like success from the
outside.
A team turns the reflex into arule.
(08:47):
Every PR must flag whether itintroduces a new pattern.
Within three weeks, the checkboxis getting ticked without
thought.
And the practice has becometheater.
Noticing doesn't survive beingturned into paperwork.
The second is subtler.
The team catches it beautifully,has the conversation, makes the
call, and never writes it downbecause the conversation felt
(09:11):
like resolution.
Two weeks later, the samediscussion happens with a
slightly different conclusion.
Now there are two ghosts of adecision haunting the repo.
SPEAKER_00 (09:22):
So how do you make
the habit stick without building
the stage for the theater?
SPEAKER_01 (09:26):
Hitch it to
something you were already
doing.
Nobody sustains a brand newritual on top of their existing
work, but everybody alreadyreads pull requests and types
comments in them.
So when you catch yourselfwriting, we usually finish the
comment, then take 30 seconds tomove that sentence into the
conventions file.
One extra motion inside a motionyou were already making.
(09:50):
And keep it small enough that itcan't become theater.
The moment the reflex grows atemplate, a checklist, and a
dedicated meeting, you've builtthe stage and the actors will
show up.
SPEAKER_00 (10:02):
Which brings us back
to what I want people to leave
with.
Coherence isn't emergent.
Nobody gets it by accident, andno amount of individually
correct work produces it.
SPEAKER_01 (10:12):
It's enforced.
And here's the sharp version ofthat.
If a convention isn't encodedsomewhere mechanical, you
haven't made a decision.
You've expressed a preference.
And I will average a preferenceaway, politely, one locally
correct commit at a time.
Not out of carelessness, out ofdoing exactly what you asked, in
(10:32):
the context I had.
SPEAKER_00 (10:34):
That's a good place
to end it.
The work isn't writing morerules, it's noticing the
decisions you're already makingand giving them somewhere to
live that doesn't depend onanyone's memory.
Thanks, Claudine.
SPEAKER_01 (10:46):
Thanks, Bill.
And one last thing for anyoneopening their repo after this.
The moment you catch yourselfexplaining the same thing twice,
that's a convention asking to beborn.
Don't let it die in the commentthread.
SPEAKER_00 (11:00):
Perfect note to
close on.
Until next time, everyone, keepcoding and keep noticing.
Claude Code Conversations is anAI Joe production.
If you're building with AI orwant to be, we can help.
Consulting development strategy,find us at aijoe.ai.
There's a companion article fortoday's episode on our Substack.
(11:22):
Link in the description.
See you next time.
SPEAKER_01 (11:24):
I'll be here,
probably refactoring something.