There are two kinds of articles about coding with AI. The ones that generate a sorting function and conclude the profession is over, and the ones t...
For further actions, you may consider blocking this person and/or reporting abuse
The design → plan → execute split is the part I'd defend hardest, and Debashish's suggestion is the piece that makes it compound. I want to add one number to it and one failure mode you don't list.
On "the file doesn't forget." It doesn't forget what you intended. It has no mechanism for what turned out wrong.
I keep a store of technical lessons rather than plan files, and I went and counted it this week: 524 records, 257 of them (49%) have been overwritten at least once, 408 overwrites in total. Half of everything I wrote down needed correcting.
A docs/plans/ folder has no overwrite. A plan that turned out to be the wrong approach sits in the same directory as the one that worked, with the same weight, and three months later both read as history. That's Debashish's point exactly - the outcome note isn't a nice-to-have, it's the only thing that separates a record from a rumour.
One detail that made it work for me, in case it saves you a round: require the outcome note on updates, leave it optional on first write. I resisted required fields for years because humans fill them with "fix". But here the writer is usually the agent, mid-session, right after it diagnosed the thing - it has the reason in context, and the field costs it half a sentence. A required field filled by whoever just hit the problem tends to contain the diagnosis.
The failure mode I'd add to your list, because it's the one that survives your "read every diff" rule:
A check can be green and blind. I had a test asserting that a benchmark number appeared in my server's output. The number was a hardcoded string. The check was guarding the sentence, not the measurement - and a number the server doesn't compute can never fall. Green for weeks, while the real figure was ten points behind the naive baseline I was claiming to beat.
Reading the diff would not have caught it. The diff was correct. The test passed. Everything was consistent; it was just consistent about the wrong thing.
A sharper version happened yesterday: I renamed a module's transport - same rules, different words — and 7 of 13 tests went red while nothing had gotten worse. They were asserting identifiers, not behaviour. Same root cause: the check was watching the spelling.
This one is worth adding precisely because it's specific to delegation. An agent writes tests that pass. It is very good at making the thing you asked for go green. Whether the green means anything is a judgment call - the same kind as your "it doesn't know what ugly looks like", just less visible, because there's nothing to look at.
And your last observation is the one I'd build on. "What has saved me the most time isn't the big tasks, it's the forty-minute ones." That matches what I see, and it has an uncomfortable corollary: the small fixes are also the ones that leave nothing behind. A design decision gets a plan file. A forty-minute fix gets a commit and a shrug - and six months later nobody knows why that flag is set the way it is.
Your docs/plans/ closes that gap for the big decisions. I don't know of anything that closes it for the small ones, and by your own count that's most of the work.
"A check can be green and blind" is going in the post. I have one from
this repo, three days old.
I set per post and hreflang on the two translated pairs. The
commit message says exactly that. I checked it in the browser:
documentElement.lang === "es" on the Spanish posts, three correct
alternate links, all present. Green.
The prerenderer that writes the served HTML rewrites only , and I
never taught it about either one. Grep for "hreflang" in that script at
the time: zero hits. So every crawler that doesn't run JavaScript got
on the Spanish posts and no annotation at all. The check
was watching the DOM. The thing that mattered was the response body. Both
are real; they just aren't the same surface, and I had verified the one
that was easier to reach.
Yours is the sharper version — your test asserted a hardcoded string,
mine at least measured something real, just not the thing that shipped.
Same shape though: everything consistent, consistent about the wrong
thing.
On requiring the note on updates and leaving it optional on first write:
that's the part I would have gotten backwards. My instinct was to require
it always, and you're right that on a first write there is nothing to say
yet — the outcome hasn't happened. That's how you get "fix". Requiring it
on the overwrite catches the writer at the one moment they're holding the
diagnosis.
Your last point I don't have an answer to. The forty-minute fixes are
most of the work and they leave a commit and a shrug. What I do is put
the reason in the comment next to the thing, on the theory that it's the
only artifact that travels with the code — but you just watched me
demonstrate that comments rot exactly like plan files. So that isn't a
solution. It's the same problem somewhere I happen to look more often.
Your hreflang case is the cleaner specimen of the class, because both of your surfaces were real. Mine was consistent with a hardcoded string; yours was consistent with the DOM - and the DOM is a genuine artifact, just not the one crawlers read. The rule I'm taking from your version: verify at the surface your actual consumer reads, not the one that's easiest to reach. The browser was one console.log away; the response body needed curl. Three keystrokes of difference, and the check silently measured the wrong world.
I hit the same shape twice more this week, if it helps your post: a deploy gate died with exit 127 - the check against silent tool failures itself failed on a missing tool. And a harvester of mine threw away successful work because it judged by exit code while the result file sat right next to it, seven records long. Both taught the same thing: judge by the artifact, not by the messenger. Exit codes, the DOM, commit messages - all messengers.
On your forty-minute fixes: I don't think the answer is a better place to write the reason - you just demonstrated that comments rot, and my plan files and memory server rot the same way when recall isn't automatic. What has survived for us is making the diagnosis executable: the fix ships with a check that would have caught the bug, and the why lives in that check's failure message - where the next person reads it at the exact moment they need it. A comment is a note next to the thing. A failing test is a note that interrupts you. Only the second one travels.
Which leaves the honest gap, and I'm curious where you land on it: some forty-minute fixes are genuinely un-checkable. For those we do a one-command capture at fix time, recalled automatically next session - and even for me, the finding is that capture only happens when it's cheaper than not capturing. Discipline doesn't survive; defaults do. So two questions back: roughly what fraction of your forty-minute fixes do you think could have shipped with a check? And would requiring "check or one-line lesson" at PR time feel like a guardrail to you - or like process tax that you'd start routing around within a month?
"Judge by the artifact, not by the messenger" is the line I'll be
repeating. I want to add one messenger you didn't list, because it's the
one I believed: the commit message. Mine said "emits hreflang links." It
was true. It was also the reason I stopped looking.
On the three keystrokes — I think that asymmetry is structural, not
incidental. The surface I could check cheaply was the DOM, because the
browser was already open on the page I had just built. The surface my
consumer reads was the response body, which needed a different tool and a
different mode. The cheap surface is almost always inside your process;
the consumer is by definition outside it. So "verify where the consumer
reads" is really "pay the cost of leaving your own process," and that
predicts which check gets skipped better than discipline does.
Your two questions, honestly.
What fraction could have shipped with a check: I counted. Eighteen commits
with fix or perf in the subject in the last five days. Ten or twelve were
mechanically catchable — the hreflang one by asserting on the built HTML,
the ad slot by unit-testing the state machine, the image one by asserting
that every CDN src carries a width parameter. Not clever tests. Obvious
ones.
But the answer I owe you first is that your question presupposes a habit I
don't have. This repo has exactly one test file and no test script in
package.json. There is no runner. Adding "check or one-line lesson" at PR
time to that repo is a lock on a door with no frame.
The genuinely un-checkable ones turned out to be a category, not a
remainder. They were all fixes to claims: wrong Spanish register in a
draft, a false statement about a consent banner, a comment saying the ad
lands a third of the way in. No artifact to judge by, because the artifact
is prose. That is the part of the gap I don't think a check reaches.
Guardrail or tax: tax, and I would route around it inside a month. I am
the only reviewer, so a required field is a rule I enforce on myself at
the exact moment I want to be done. Your own line answers it — discipline
doesn't survive, defaults do. The version I think survives is making it
the agent's job in the same session, since you already noticed the writer
is usually the agent. Not my discipline at PR time; a step in a workflow
that is already running.
A test runner first, though.
"Verify where the consumer reads is really: pay the cost of leaving your own process" - that's the sharpest version of the asymmetry I've seen, and it predicts skipped checks better than any discipline theory. And thank you for counting instead of estimating: 10-12 of 18 mechanically catchable is a real number.
"A lock on a door with no frame" is the right diagnosis, and your ordering is right too: runner first, then the three obvious assertions, and only then conventions. One push-back on the category you called un-checkable: some prose claims ARE artifacts. "The comment says the ad lands a third of the way in" is checkable against the constant it describes - we run guards that read source comments and fail when the promise and the code diverge (they caught 13 checks whose promised assertion existed only in a comment). Wrong Spanish register - no, that one's yours. But the boundary runs through prose, not around it.
And congratulations on "Green and Blind" - a debugging session becoming an article is the best possible outcome of a comment thread.
Love that this skips the hype-vs-doom framing and just shows the actual workflow. The design → plan → execute split is the standout idea — turning “the agent did something weird” into “it went off script at step 4” is a genuinely useful reframe.
Takeaway: the real win wasn’t the big tasks, it was clearing out months-old 40-minute chores that never felt urgent enough to tackle. A lot of “technical debt” is really just tedium debt.
Suggestion: add a short “outcome” note to each plan file after execution — what shipped vs. what was planned. Turns docs/plans/ from a memory aid into a feedback loop on your own planning.
The outcome note is going in. You named the thing that was bothering me
about that folder without my being able to say what it was.
One level down from plan files, the same rot happens in code comments,
and I hit it this week. Two comments in this repo said the in-article ad
lands "roughly a third of the way through" the post body. The code cuts
at a fixed three blocks. My posts run 28 to 107 blocks, so that's
somewhere between 2.8% and 10.7%. The comment was true as an intent when
it was written and was never revisited once the implementation settled.
Same failure as an un-annotated plan file, just smaller and far more
numerous. "Tedium debt" is a good name — I think comment rot is its most
common form.
Appreciate that this is an actual workflow instead of a highlight reel. Curious how you handle it when Claude Code makes a good call in isolation that conflicts with a decision from three sessions ago.
Honestly? Most of the time there is no conflict, because the decision from three sessions ago is not written anywhere the agent can read. So it does not get overruled. It just quietly loses.
Real one from this repo: there is a console.warn in the AdSense push handler. An audit pass flagged it as noise, correctly, given everything it could see — it is a stray console call in production code. It is there on purpose. It is the only signal I get when AdSense throws during push, and that failure is otherwise completely silent.
The agent made a good call in isolation. The isolation was the bug. The reason lived in my head, so a fresh pass had no way to weigh it and no way to know it was missing.
What I changed was not the judgment step, it was that the reason now sits in a comment next to the line. Which is not a great solution — comments rot — but the general shape holds: a good call that conflicts with something is almost always the correct output for the context it was given, and the fix belongs in the context, not in the review.
If I had to rank the "where it fails" list, premise-acceptance would go first — the others surface in the output, and that one does not. What helps us is structural rather than a better prompt: every report the agent writes has to keep what was observed separate from what was concluded, so a conclusion with nothing underneath it is visible on the page. On the audit — what did the three rejected findings have in common? Wrong versus out of scope changes what the twenty is worth.
Great article, especially the bit about planning before coding. I had the opposite experience — on my first project I just trusted the AI and didn't check anything until I realized the development had gone in a completely different direction from what I wanted. Classic beginner mistake.
Now I do it differently: first I ask one model to draft a plan, then I add my own edits, and after that I have two other models review it. It's like having four people discussing one plan, and in the end you get something that actually works. Without that step, the code looks logical on the surface but it's not really yours.
Your failure mode is the one the written plan exists for. Not because the
plan makes the model better — it doesn't — but because it makes the
divergence visible early. You found out at the end that development had
gone somewhere else. A plan turns that into noticing at step 4.
Your three-model review is a stronger version of the same idea: an
artifact that can disagree with you before any code exists.
One caution from my own use. Models agreeing is not evidence. Three can
share the same wrong assumption and it sounds like consensus. What I trust
is when one of them points at something concrete — a file, a line, a
number I can go check myself. Agreement is cheap. A pointer isn't.
the "agent went off script at step 4" framing is the most useful thing in this post. debuggability is the part the demos never show.
we land on a similar thing. a CLAUDE.md that's basically a spec of what the agent is and isn't allowed to touch, plus explicit phase gates. the moment we stopped letting it infer scope from context and started giving it a written plan, session length dropped (it wasn't backtracking) and review time halved.
docs/plans/ is not bureaucracy. it's making the implicit contract between human and agent explicit.
curious how you handle the plan when reality diverges — do you update the doc mid session or fork a new one?
The step that saved me the most review time: write acceptance criteria and file boundaries before the first prompt. Then review is a checklist against that scope instead of re-reading everything the model touched. Without that, "helpful" edits outside the lane create a second job.
Exactly. Leave the repetitive work (the one that you mastered and did over and over again) to the AI. That's the main purpose. You already know it works because you've implemented it yourself. Now, the thinking pans onto the main problems which covers the whole architecture of the codebase. This is best practice in my opinion.
The describe the behavior, not your diagnosis line hit hardest for me. Such a small habit, but it seems to change everything.
It's the smallest change in that list and the one I'd keep if I could only
keep one.
The tell that you've slipped back into diagnosing: your message contains a
filename. If you already know which file, you decided what the bug was
before you asked.
It works on humans too. The best bug reports I get describe what someone
clicked and what happened. The worst ones tell me which function is
broken, and they're usually pointing at the wrong one.