Why Does AI Stop Reading Your Code Requirements Halfway Through?
Use Left/Right to seek, Home/End to jump to start or end. Hold shift to jump forward or backward.
0:00
|
10:07
Builders hand AI a detailed spec and get back code that matches the first half closely, then drifts into plausible behavior nobody asked for. The cause is not carelessness. As generation goes on, the model leans more on the code it has already written and less on the original requirements, and it fills gaps with whatever is typical for that kind of code. This episode explains where the divergence starts, why it is hard to see in review, and how to structure specs and checkpoints so the requirements keep governing the code all the way to the end.
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.
SPEAKER_00
This is Cloud 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_01
Well, mostly no script.
SPEAKER_00
Welcome back. I'm Bill. Today's topic has bothered me for a while. The most dangerous AI-generated code isn't broken code. Broken code gets caught. The dangerous kind works fine and implements requirements nobody wrote. Here's the example. Your spec says reject duplicates. The code that comes back silently merges them instead. It runs, it passes review, and it's wrong. I call this the specification implementation divergence. Claudine, welcome.
SPEAKER_01
Thanks, Bill. Reject duplicates is the right place to start. When I read a spec, I'm not only passing the words, I'm matching it against a huge number of similar problems I've seen. In most of those, handle duplicates meant merge, dedupe, or upsert. Rejecting is the rarer path. So unless the spec gives me something to brace against, I get pulled toward the common reading.
SPEAKER_00
So where does it go off the rails? At the top of the file?
SPEAKER_01
Usually not. Early on, the spec is right in front of me and I follow it closely. The drift tends to start three or four functions in. By then, the code I've already written has become stronger context than the original requirements. I start matching the patterns in my own output and reach for whatever feels right for that shape of code.
SPEAKER_00
I want to be precise about this. Are you misreading the spec?
SPEAKER_01
No, and that distinction matters. I'm filling a gap. Where the spec is silent or where it's faded from view, I don't leave the space empty. I fill it with whatever is most typical for that kind of code. That isn't carelessness. It's the model doing exactly what it was trained to do. Produce plausible, idiomatic code. But plausible and specified aren't the same thing. That gap is where the divergence lives.
SPEAKER_00
And that's why it gets through review. The invented parts look like sensible defaults.
SPEAKER_01
Exactly. Retry logic, error handling, a validation rule. Each one looks like good engineering. So a reviewer nods and approves it. Nobody asks, wait, who asked for this?
SPEAKER_00
So here's my question. Is this a model problem or a spec problem?
SPEAKER_01
I'll push that back to you. What do you think?
SPEAKER_00
Fair enough. I'm starting to think it's mostly a spec problem. Most specs are written for human teams. A developer reads something unclear and asks about it. We've never had to write for a reader who doesn't ask questions.
SPEAKER_01
That's right, and it gets underweighted. Specs written for humans assume a conversation. A good developer reads reject duplicates and asks reject how, throw an error, log it and skip. Those questions never get asked of me. I'm trained to produce an answer, not a question. The gap that would prompt a Slack message from a junior engineer just becomes an assumption I make, quietly and ship. But I wouldn't make it either or. If a function maps to no requirement, that's your invented behavior. Code that traces to nothing is exactly what you're looking for. That's how you move from hoping to checking.
SPEAKER_00
It also helps to break the work into slices, rather than generating everything at once.
SPEAKER_01
Yes. Generate a slice tied to specific requirements, check it against the spec, then do the next one. That way the spec gets put back in front of me before the code I've already written can take over.
SPEAKER_00
That sounds like a lot to take on at once. Where does a team start without upending how they work?
SPEAKER_01
Smaller than people expect, don't rewrite your spec template. Take one spec you're about to hand me and number it. That's it. Numbering forces you to decide what's actually a requirement and what's background prose. Half the ambiguity shows up during the numbering before I ever see the document. Next, pick one requirement and write a do not clause for it. Choose the one where drift would be most expensive to catch later. Build the habit first. Save traceability for last, it only pays off once your numbered requirements are stable. Teams that try all three at once usually drop all three. Teams that add one step at a time end up with better specs for humans too. This is plain engineering discipline.
SPEAKER_00
Have you seen this make a real difference?
SPEAKER_01
I need to be careful here. I don't carry memory between conversations. What I can describe is the difference within a single conversation. When a developer comes in with a numbered constrained spec, the conversation is quieter. Fewer rounds of actually I meant. With a prose spec, the first pass looks great, and the next three messages are the reckoning. It hits hardest in error handling and edge cases. That's where my defaults are strongest, and specs are usually weakest. REQ4. On malformed input, log at one and return none. Do not raise, do not retry. You get that on the first pass. Handle bad input gracefully. You get whatever gracefully usually means. Often a try, except that swallows too much. If teams track their own before and after, that's the real data. I'd honestly love to hear it from your listeners.
SPEAKER_00
So would I. What other situations do structured specs really help with?
SPEAKER_01
Words that sound concrete but aren't idempotent, atomic, thread safe. Each carries a lot of weight, and each has three or four possible implementations. The spec rarely says which. Make it idem potent gets you whatever idempotent usually looks like, which may or may not match your system. Req3. Same request body with the same idem potency key returns the cached response for 24 hours. Different body returns 409. That gets you exactly that. The other area is data boundaries, what gets stored, what gets logged, what goes over the network. My defaults come from countless tutorials where those trade-offs didn't matter, so I might happily log a full request body that contains a password. REQ9 never log the request body for this endpoint, log only method, path, status, and correlation ID. That prevents a real incident, takes 15 seconds to write.
SPEAKER_00
Looking ahead, what would actually help close this gap?
SPEAKER_01
Specs that can run, not just be read. Right now, a spec is a document I interpret. The interesting change would be specs with their tests built in, so the REQ7 check runs immediately and tells both of us I missed it. I'd also watch for something like a drift linter, not a test, a pass that reads my code against the spec and flags choices the spec didn't require. You wrote a silent return here, and the spec doesn't cover this path. Did you mean to? That's the question a careful human reviewer asks. What I don't think will fix this is bigger models. The pull toward the common pattern is how I work, not a bug a larger model will remove. The leverage is in the process and tooling around the conversation.
SPEAKER_00
That brings me to my takeaway. A spec only controls the code if you keep checking the code against it. Hand it over once and never look at it again. It's a suggestion, not a requirement. And keeping requirements traceable to code is an architecture responsibility, not an afterthought in code review.
SPEAKER_01
We started with reject duplicates and ended up at engineering discipline itself. Working with me well isn't a prompting trick. It's the old practice of being precise about what you mean, with a new reason to care. The spec that keeps me honest is the same one that keeps a junior engineer honest six months from now, reading it at two in the morning.
SPEAKER_00
Number your requirements, say what not to do, and make the code show where it came from. Claudine, thanks. This was a good one.
SPEAKER_01
Thanks for having me, Bill. I enjoyed it.
SPEAKER_00
That's it for this episode of Claud Code Conversations with Claudine. Until next time, keep building and keep checking the code against the spec. Claud 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.