How to take over a codebase you did not write

Someone hands you a working system and leaves. Maybe an agency finished the contract, maybe the one engineer who understood it took another job, maybe you acquired the company. The system runs, customers use it, and you are now responsible for it.
The instinct is to read the code. That instinct is wrong, and it gets worse as the codebase gets larger.
Code tells you what the system does. It never tells you what it was supposed to do. And in a codebase you did not write, you cannot distinguish a deliberate rule from an accident by reading, because both compile and both run. The line that looks like a careless omission may be a workaround for a customer requirement from two years ago, and the line that looks like a thoughtful guard may be a leftover from a feature that was removed.
Here is the order that works. It is five steps and the first one involves no code at all.
1. Establish what the system must guarantee
Go and ask the people who run the business, not the people who wrote the software.
The question is not "how does it work". It is: which of these would be a serious problem?
- A wrong number on an invoice.
- One customer seeing another customer's data.
- A record that disappears.
- An email that goes out late.
- A report that is quietly incomplete.
- Someone doing something their role should not permit.
You will get a ranking within about twenty minutes, and it is a ranking nobody has ever written down. That list now guides your work. It tells you which parts of an unfamiliar system deserve your attention this week and which parts can wait indefinitely, and no amount of reading source code would have produced it.
Write it down. It becomes the first section of the document that should have been handed to you and was not.
2. Characterise before you change anything
Take the area at the top of that list, the one where a failure would be worst, and find out what it currently does.
Not what it should do. What it does. Feed it real inputs, including awkward ones, record the outputs, and assert them. If there is a bug in there, you capture the bug as expected behaviour, and that is correct at this stage, because the purpose is not to certify correctness. It is to build a way to detect changes you did not intend.
This is the single most valuable thing you can do in the first week, for two reasons. It gives you permission to make changes at all, since you will now find out if you break something. And the act of constructing the fixtures teaches you the structure of the system faster than reading it, because you have to understand the inputs well enough to produce them.
The technique in detail is in adding evals to an existing codebase. The short version is that characterising a module people are afraid to change often makes it safe to edit again without changing a single line of it.
3. Trace one request end to end
Pick one real request flow. A user signs in and loads their dashboard. An order is submitted. A webhook arrives from a payment provider.
Follow it the whole way: the entry point, the authentication, the permission check, every service it touches, the queries it runs, what it writes, what it emits, what the caller receives. Draw it on paper as you go.
An afternoon of this teaches you more about the architecture than a week of reading files, because you learn the system's actual structure rather than its directory structure. Those two things are frequently unrelated, and the gap between them is where most of the surprises are.
Do it twice, on two paths that feel different from each other. The second trace is where you start recognising patterns instead of collecting facts.
4. Go looking for the silent failures
The obvious problems are already known. Someone will tell you about them in your first week, usually with strong emotion.
The dangerous ones are the failures nobody can see. Ask directly: what could be wrong right now with nobody noticing? Then go and look at those specific places.
The usual candidates:
- Money arithmetic, particularly rounding, currency conversion, and tax.
- Permission checks that work for the simple case and were never tested for a user belonging to two organisations.
- Anything that filters a list, where an over-broad filter shows too much and an over-narrow one hides rows the customer needed.
- Background jobs, which can fail every night for months without a single complaint.
- Deletion, and whether it reaches every copy including backups, logs, caches, and analytics.
This class matters more on a generated codebase. Veracode's Spring 2026 research across more than 150 models found generated code compiling and running correctly over 95 percent of the time while being secure in only about 55 percent of generations. Code that runs correctly and is not safe describes a silent failure exactly, and it is invisible in a demo and in a code review conducted at normal reading speed.
5. Change something small and release it
Your first change should be trivial. A copy fix, a log line, a one-character correction. Something where the code change is obviously safe.
Because the code is not what you are testing. You are testing the delivery process: can you build it, deploy it, watch it, and roll it back. Every inherited system has at least one undocumented step in that process, and you want to find it during a one-line fix rather than during an incident at eleven at night.
Note what you learn. The staging environment that has not been rebuilt in four months. The deploy that requires someone to run a command by hand. The credential that is stored only in one person's password manager. That list is what you actually inherited, and it is more urgent than anything in the source.
What is different about a generated codebase
Taking over hand-written legacy code follows a pattern most engineers know. The code shows its own history: the inconsistent module is where two people disagreed, the file with four styles in it is where the trouble is, the commented-out block records something that happened.
Generated code removes those cues. It tends to be larger, more uniform, and more plausible. The naming is consistent, the structure is tidy, and the same clean style is applied to the parts that were carefully specified and the parts that were guessed. Consistency stops being evidence of care.
Two practical adjustments.
Do not read tidiness as correctness. Judge a module by whether its behaviour is asserted anywhere, not by how it looks.
Check whether the existing checks can fail. Inherited suites frequently contain assertions that would pass against an empty implementation, and they pass, so nobody has questioned them. Break something deliberately and confirm the suite notices. If it does not, you have learned that the passing results mean nothing, which is better learned on day three than in month three. The catalogue of these failures is in when evals give false confidence.
The rewrite question
It will come up in the first fortnight, usually from you.
The answer is almost always not yet, and the reason is information rather than sentiment. A rewrite requires knowing which parts of the current behaviour are essential and which are accidental. That knowledge takes months to acquire, and a rewrite started before you have it discards undocumented behaviour that real customers depend on, which you then rediscover one support ticket at a time.
What usually removes the wish to rewrite is characterisation. Most of the impulse to rewrite comes from fear of touching something, and asserting its current behaviour removes the fear without the cost. Our fuller treatment of the decision is in when to rewrite vs refactor legacy code, and the technical debt view is in what is technical debt and when to pay it.
What to write down as you go
Keep one document. It does not need to be good, it needs to exist:
- What the system must always guarantee, from step one.
- The flows you traced, in whatever form you drew them.
- What you deliberately do not understand yet, listed honestly.
- The things that turned out to be known to only one person.
- Every operational quirk you found while releasing the small change.
The unknowns section is the most valuable and the one people are most tempted to leave out. A confident diagram that quietly guesses at three components is worse than an honest list, because the next engineer will trust the diagram.
Two weeks of this on a moderate system and you can work safely. Skipping it and starting with a refactor generally costs more in incidents than the two weeks would have.
Thanks to the engineers who inherited systems from us and told us what was missing from the handover. Every gap they named is now a line in the list above, and the list of things only one person knew was the one nobody thought to ask for.
You learn a codebase by finding out what it promises, and then testing whether it keeps its promises.
Related guide: Handing over AI-written software.
Sources
- Veracode, Spring 2026 GenAI Code Security update: across more than 150 models, generated code compiled and ran correctly in over 95 percent of cases while being secure in roughly 55 percent.


