Most writing about AI-assisted development argues about whether it works. That question is settled in my experience: it works, and the projects described elsewhere on this site would not exist at this scale without it. The more useful question is what it costs, and the answer is not the one people usually expect. The cost is not bad code. The cost is a specific category of failure that traditional engineering practice had quietly solved, and that AI-assisted development reintroduces.
This essay is about that category, drawn from real incidents across these projects rather than from general concern.
Industry standards exist because someone already made the mistake
A professional codebase is surrounded by machinery that catches errors before a human ever sees them. Compilers reject type mismatches. Linters flag unreachable branches. Code review catches intent that does not match implementation. Framework conventions make some mistakes impossible to express. None of this machinery is glamorous, and most working developers stop noticing it, the same way you stop noticing a handrail.
The reason it matters here is that AI-assisted development produces a lot of artifacts that fall outside all of it. Configuration files. Frontmatter. Deployment scripts. Glue between systems. SQL run through a command-line tool. Markdown that controls behavior. These are exactly the places where nothing checks your work, and exactly where an AI, working fast and plausibly, will hand you something that looks right and does nothing at all.
The failure mode that defines the category
The clearest example from my own work involved a text-formatting tool I built to strip AI writing patterns out of documentation. It had a configuration block at the top of its file declaring its name, its description, and its permitted operations. Every field was correct. The syntax was valid. It sat in the directory I believed was correct.
It did nothing for months.
The tool needed to live in one directory to be invoked manually, and a different directory entirely to be invoked automatically. In the first location, the configuration block I had carefully written is simply ignored, it is not read, not validated, and not reported as a problem. There was no error message, because from the system’s point of view nothing had gone wrong. A file with perfectly valid configuration sat in a folder where configuration is inert.
There is no compiler for this. There is no linter for this. There is no code review that catches it, because the file reads as correct to any reviewer who does not already know the specific rule. And when I later tried to rename that tool, I made a variant of exactly the same mistake: I edited the name field in the configuration, confirmed it looked right, and reported that the rename was complete. It was not. The invocable name comes from the filename, never from the configuration. I had to make the same class of error twice before the lesson stuck.
Silence is the common thread
Once I started looking for this pattern, it turned up everywhere across these projects.
A deployment verification step read a compiled binary, searched it for a string proving the fix was present, and reported the fix as missing. It reported this three separate times, across three successful deployments. The deployments had all worked. The verification was using a pattern-matching approach that produces false negatives against binary data. A full debugging cycle went into chasing a deployment problem that did not exist, because the tool that was supposed to catch problems was itself the problem.
A database script containing ordinary typographic punctuation, the kind a word processor inserts automatically, deployed with no error and quietly wrote corrupted characters into seeded data rows, because the command-line tool that runs SQL scripts reads files in an older character encoding than the one the file was saved in. Nothing failed. The data was simply wrong.
A script that ran without specifying which database to connect to created its tables in the server’s default administrative database instead of the application’s. The script succeeded. It succeeded in the wrong place.
A conditional block in a page template broke because a comment was placed between one branch and the next. The template engine treated the comment as content, which silently terminated the conditional chain, causing every subsequent branch to render at once and the branch condition itself to appear on screen as visible text. Valid syntax, no error, completely wrong output.
A background retry loop with no memory of prior failures retried a broken operation every few seconds for five weeks, writing an error log entry every time, until the log table had grown to roughly forty-one gigabytes. Nothing crashed. Nothing alerted. The system was, in a narrow sense, working exactly as written.
None of these produce a stack trace. All of them produce confident, plausible, wrong behavior. And a fast development loop, which is the main thing AI assistance buys you, is very good at generating more of them before you notice the first one.
Confident wrong answers, and the cost of one sample
The second category is subtler, and it cost me two documented conclusions that turned out to be false.
While evaluating local language models for internal use, I recorded that one model could not produce a working spreadsheet formula for a particular problem. That finding went into a comparison document and shaped a routing recommendation. On a proper retest, the same model produced the correct answer six times out of six, byte-identical. The original failure was an unlucky draw from a probabilistic system, and I had written it down as a capability limit.
The same error ran in the opposite direction elsewhere. A different model solved a genuinely hard filter-context problem on its first attempt, and I credited it with the capability. Multi-sampled properly, it scored zero out of three. I had promoted a lucky result into a documented strength.
The rule I now apply is that no single run may drive a routing, deletion, or purchasing decision. Anything that matters gets multi-sampled first, and single-sample results get labeled as indicative rather than established. This sounds obvious written down. It is not obvious in the moment, because one clean result from a system that speaks fluently feels like evidence.
Worth noting how that first error surfaced: someone pushed back on my conclusion using an outside source, and the pushback was partly wrong on its own terms. Investigating it anyway is what exposed the real methodology flaw, which mattered far more than the point being argued.
Documentation drift stops being a hygiene problem
In a conventional project, out-of-date documentation is a nuisance. A new developer reads it, gets confused, asks someone, and moves on. The code is the source of truth and everyone knows it.
That relationship inverts with AI-assisted development, because the documentation is what the assistant reads at the start of every session. Stale documentation is not a nuisance; it is an instruction to do the wrong thing, delivered with full authority, to something that will act on it immediately and without hesitation.
I ran into this concretely on the warehouse project. A committed plan described migrating one hundred forty-nine database pointers in a single maintenance window, described explicitly as all-or-nothing. A later conversation replaced that approach with an incremental migration where nothing gets re-pointed underneath a working report. Both versions now existed, one in a committed document and one in a decision that had not been written down yet.
The fix was not simply updating the document. It was writing an explicit note recording that a specific section of a specific committed document is now wrong, naming what supersedes it, and naming the trap that following the stale version would create, in this case a circular dependency that would only reveal itself at cutover, and the destruction of the parity baseline the entire validation depended on. That is a different discipline than keeping docs tidy. It treats documentation as executable, because in practice it now is.
The related habit: when a change alters behavior, the documentation update belongs in the same commit, not a later cleanup pass. I learned that one the ordinary way, by shipping several interface overhauls promptly while the project’s own guidance kept describing the previous version.
What actually works
None of this argues against building this way. It argues for specific compensating discipline, which I now apply consistently.
Verify the artifact, not the intent. After any change, check the thing that actually ships. Read the deployed binary, query the real database, open the built output. Not the source that should have produced it, and not the assistant’s report that it succeeded.
Assume silence means untested. The absence of an error is not evidence of success anywhere in this category. If a step produced no output at all, that is the step to check first.
Write down the mistakes, not just the fixes. Every project here keeps a running lessons file recording what went wrong and what rule came out of it. This is not sentiment. Those files are read at the start of future sessions, which makes them the mechanism that stops a mistake from recurring.
Discuss before code, every time. The plan gets described in plain language and approved before any file changes. This resets every session, even when the same fix was already agreed in a previous one. The constraint is not about trust; it is about maintaining a mental model of a system that is being built faster than any one person would naturally absorb it.
Multi-sample anything probabilistic. One good answer from a language model is an anecdote. Decisions need more.
What it does not fix, which is most of the timeline
One more thing belongs here, because it is the correction I would most want made if someone read the rest of this site and concluded that AI-assisted development compresses delivery.
Getting the first application I built this way to a state where an operational team fully relies on it is roughly a year’s work, and it is partway through. The code is not the part taking a year. A small team has long lead times for anything entering production, and those lead times barely notice how the code was written: review cycles, security and access approval, coordinating with people whose main job is something else, testing against real workflows with the people who perform them, training, and waiting for a moment in an operational calendar when changing a system people depend on is acceptable.
None of that accelerated. What the tooling changed was whether the application could exist at all given who was available to build it, which is a different and narrower claim than making delivery faster.
This matters for the failure modes above as well. A development loop that produces working code quickly, sitting inside an organisation that moves at its own pace, creates a specific hazard: a long gap between when something is written and when it is genuinely exercised by real users. Every silent failure described in this essay is easier to introduce and slower to discover under exactly those conditions.
The honest summary is that AI-assisted development moves the difficulty rather than removing it. Less time writing code, considerably more time verifying that what got written does what it appears to do. That trade has been worth it for me, several times over. But it is a trade, and pretending otherwise is how the forty-one gigabyte log file happens.