AI Will Update One File and Break Three Others. Here's the Fix.
You ask AI to update an API endpoint. It updates the controller cleanly. What it does not update: the frontend service call that hits that endpoint, the middleware that validates the request shape, and the TypeScript interface that both sides depend on. The endpoint works in isolation. The system breaks. You spend forty minutes figuring out why.
This is not an edge case. It is the default behaviour of AI coding assistants when working on anything with more than one moving component — frontend and backend, service and middleware, schema and model. The AI sees what you show it. It does not see what connects to what. And it almost never asks.
Why it happens
AI coding tools solve the problem directly in front of them. You paste a function, describe a change, and the model produces a fix for that function. This is genuinely useful — the fix is usually correct, often better than what you would have written, and fast. The failure mode is not that the fix is wrong. It is that the fix is incomplete, and the model does not know it.
The model has no internal representation of your system. It cannot see that the function you changed is the source of truth for four downstream consumers. It cannot see that the data contract it just modified is assumed in six other files. It can only reason about what is in its context window — and most of the time, the dependent files are not there.
The result is what engineers call a cascading miss: one correct change, several silent breakages, no error until runtime.
The specific failure modes
Three patterns come up repeatedly:
Contract drift. You change the shape of an API response — add a field, rename a key, restructure a nested object. The backend is updated. The frontend still expects the old shape. Everything compiles. Nothing works at runtime.
Validation gaps. You add a new required field to a form or request body. The backend validates it correctly. The middleware that sits between frontend and backend does not know about the new field and strips it before it arrives. The validation always fails, and the reason is three layers away from where you are looking.
Model-schema divergence. You update a database schema — add a column, change a type, rename a table. The migration runs. The ORM model that maps objects to that table still has the old definition. Queries that should work now throw exceptions at the model layer.
In each case, the individual change is correct. The systemic picture is broken. And because AI produced the change confidently, there is no obvious moment where something felt wrong.
The fix: a living architecture document
The solution is a single document — call it ARCHITECTURE.md or CURRENT_STATE.md — that describes your system in enough detail that any component change can be cross-referenced against every other component it touches.
The document contains three things:
- Component map. Every meaningful file or module in the system, one line each: what it is, what it owns, what it exposes. Not exhaustive documentation — just enough to trace connections. "api/routes/orders.js — defines POST /orders endpoint, validates via orderSchema middleware, returns OrderResponse type"
- Interface contracts. The data shapes that cross component boundaries — request bodies, response shapes, shared types, database table definitions. These are the exact things that break silently when one side changes and the other does not.
- Dependency map. Which components depend on which contracts. When a contract changes, you can immediately see what else needs to change with it.
The document is not written once and forgotten. It has two rules that make it work:
- AI reads it before every change. Before touching any file, the AI reviews the architecture doc to understand what the target component connects to and what a change might propagate into.
- AI updates it after every change. If the change alters a component's interface or a shared contract, the doc is updated as part of the same task — not later, not separately.
How to implement this
Start with the version of this document that takes twenty minutes to write, not the comprehensive one that takes two days and never gets finished.
- List your components. Open a blank file. Write down every major piece of your system — every layer, every service, every file that more than one other file imports from. One bullet per component, one sentence describing what it owns.
- Mark your contracts. For each component, note what it exposes to others and what it expects from others. API endpoints, shared types, database tables, event schemas. These are the things that create coupling — and the things that break silently when they change without notice.
- Add the manual approval step. Before AI makes any multi-file change, ask it to output a list first: "List every file you need to change and why, before making any change." Review the list. If something is missing from it, add it. Only then approve execution. This turns AI's local certainty into your systemic review.
- Make updating the doc part of the definition of done. A change is not complete until the architecture doc reflects it. This is the discipline that keeps it useful — without it, the document drifts from the system within a week and becomes useless.
The manual approval step is the one most people skip because it feels like extra friction. It is the most important part. AI's confidence is not the same as correctness at the system level. Making the change list explicit — and reviewing it before execution — is what catches the missed files before they become broken deployments.
What changes when this works
The shift is from AI as a fast executor to AI as a system-aware collaborator. Without the architecture doc, you are handing a capable engineer a single file and asking them to make a change without telling them what the file connects to. With it, you are handing them the same file plus a map of the whole building.
The speed advantage of AI does not disappear — it gets more reliable. You stop losing the time saved on the change to the time spent debugging the breakage. The work that felt fast stops being expensive.
The pattern that causes most AI-assisted development failures is not bad code. It is correct code, written in isolation, that breaks a system it was never shown.
Frequently Asked Questions
Why does the AI confidently make a change that silently breaks other parts of the system?
AI coding tools reason only over what is present in the context window at the time of the request. If the dependent files — the frontend service call, the middleware, the shared interface — are not included in the prompt, the model has no way to know they exist or that they share a contract with the file being changed. The confidence in the output reflects correctness within the visible scope, not correctness across the full system.
What is a cascading miss and how is it different from a regular bug?
A cascading miss is a chain of breakages triggered by a single correct change: one file is updated accurately, but several downstream consumers that depended on the previous behaviour are now out of sync. Unlike a regular bug where something is written incorrectly, a cascading miss involves code that is locally valid — it compiles, passes linting, and may even pass unit tests — but fails at runtime because the system-level contract was silently broken.
What should an ARCHITECTURE.md or CURRENT_STATE.md document actually contain to prevent these failures?
The document should map which files share contracts with which others — API response shapes and which frontend services consume them, request body schemas and the middleware that touches them, database table structures and the ORM models that map to them. It needs enough specificity that an AI given that document can identify all the files that must change together when any one of them changes. A high-level overview of what the system does is not sufficient; the dependency relationships between files are what matter.
Which of the three failure modes is hardest to catch before it hits runtime?
Model-schema divergence tends to surface latest because the migration and the ORM model are in separate files with no compile-time link between them — the schema change succeeds silently, and the mismatch only throws when a query actually executes against the updated table. Contract drift in TypeScript can sometimes be caught by the type checker if interfaces are shared correctly, and validation gaps may produce obvious error responses in testing. Schema-model mismatches, by contrast, often appear as cryptic ORM exceptions that are several layers removed from the original change.