Inheriting a system is mostly a reading problem. The instinct is to open the code and start forming opinions about it, and that is the slowest possible route to being useful, because the code is the one artefact that describes what the system does and not how it lives. What you need first is the operating reality: how it gets deployed, who can deploy it, what happens when it breaks, and whether anyone would notice.
What follows is the order we read things in and why that order, the format the findings come back in, and the four failures that turn up in almost every inherited system regardless of size or stack.
What we read, in order
Roughly a day for a small system, longer for a large one, and deliberately front-loaded with the cheap signals.
- The README and any written documentation — read as archaeology rather than as instructions. The question is not what it says but when it stopped being true. A setup guide that references a service the code no longer imports dates the moment documentation was abandoned, and that date is usually when the last person who understood the system left.
- The dependency manifest and the lockfile. Two minutes, and it gives you the runtime version, the framework major, the age of the whole tree, and whether installs are reproducible at all. A missing lockfile is its own finding.
- The deployment configuration and CI files. This is the highest-value thing in the repository. It tells you how code reaches production, what runs before it does, what environments exist, and whether any of it is automated. A repository with no CI configuration is telling you that somebody’s terminal is the pipeline.
- The environment surface. Which variables the code reads, what an example file declares, and how the two differ. Configuration that exists in production but appears nowhere in the repository is the most common source of the sentence ‘it works on staging’.
- Routes and entry points. The public surface, the authenticated surface, the webhook endpoints, the scheduled jobs. This is the map, and it is where you find the endpoint nobody remembered was public.
- The data model and the migration history. Migrations are the most honest document in most projects, because they are dated and they cannot be tidied retrospectively without breaking. They show what the domain actually turned out to be, as opposed to what it was designed to be.
- Git history, read for cadence rather than content. How often, by how many people, and where. One author across the critical modules is a staffing finding, not a code finding. A long silence followed by a burst of direct commits to the main branch describes an incident you were not told about.
- The tests — first whether they run, then what they cover. A suite that does not run is worse than no suite: it is a control everyone believes exists.
- Logs, error tracking and alerts, if any exist. What is already failing in production, right now, that nobody is looking at. This routinely finds a broken feature that has been broken for months.
Only after all of that do we read the application code properly, and by then we are reading it with questions rather than opinions.
Understaffed, or genuinely broken?
This is the judgement the whole engagement turns on, and it is the one clients most need an outside answer to, because from the inside a system that has simply been neglected feels identical to one that is structurally wrong. Both produce the same symptoms: slow delivery, fear of releases, bugs that come back.
An understaffed system is coherent but behind. It has one way of doing things and it is followed. The architecture makes sense for the problem. Names mean what they say. What is missing is attention: dependencies are two years stale, the tests thinned out when a deadline arrived, the documentation stopped. Everything wrong with it is a backlog. Add capacity and it improves steadily, and the improvement compounds because the foundation holds.
A genuinely broken system is incoherent. There are three ways to do the same thing and no rule about which to use. Business logic lives simultaneously in the application, in database triggers and in a scheduled script on a server nobody has logged into for a year. The data model fights the domain, so every feature needs a special case, and the special cases now interact. Adding people to that makes it worse, because each new person adds a fourth way of doing things.
Two tests separate them faster than any amount of reading. First: can a new engineer get it running locally in a day, from a clean checkout, using only what is written down? Second: can you make a one-line change and get it to production safely — and does anyone know it worked? A system that passes both is behind. A system that fails both is not behind, it is broken, and the conversation is a different one.
When the honest answer is a rebuild, we say so, and we say what it would take either way — before anyone has paid for a rescue that will not work. The opposite finding is more common than clients expect: a system somebody has already decided to replace is often sound and needs a handful of fixes.
The risk report: severity by effort
Findings come back as a two-axis list, not a ranked one. A single ranking forces two different questions into one number and loses both.
Severity is the consequence if this stays as it is, written as a sentence about your business rather than as a label. Not ‘high’ — ‘if this host fails, the site is down until someone rebuilds it by hand, and nobody currently knows how long that takes’. Effort is a band rather than an estimate: hours, days, or weeks. Bands are honest at this stage; a precise figure before we have worked in the system is a guess dressed up.
- High severity, low effort — done first, usually in the first week. Rotate the committed credential. Turn on the backup. Add the missing index. This quadrant is where most inherited systems have a surprising amount of value sitting in plain sight.
- High severity, high effort — the roadmap. Named, costed in bands, scheduled with you. Nothing here gets started quietly.
- Low severity, low effort — done in passing, alongside other work. Never a line item.
- Low severity, high effort — written down and deliberately not done. This quadrant matters more than it looks. Publishing what we are choosing not to fix, and why, is what stops a report becoming a list of things to feel bad about.
Every finding carries the same four fields: what it is, what it costs you if it stays, roughly what it takes to fix, and what we would do. Your own team should be able to disagree with the ordering, and act on the report without us.
Stabilise before you change anything
The temptation on day one is to fix the thing that is obviously wrong. It is the wrong instinct, for a reason that is easy to state and hard to remember under pressure: if you cannot observe the system, you cannot tell whether your change helped, did nothing, or broke something two modules away that will surface next Tuesday.
So a defined set of things comes first, and none of them are features.
- A backup that has been restored from, at least once, in front of someone. Until that has happened you do not have backups, you have files.
- Error tracking and enough monitoring to know the system is up and roughly how it is behaving. Not a dashboard project — the smallest thing that alerts a human.
- A repeatable deployment that more than one person can run, from a clean machine, with no manual steps that live in someone’s memory.
- A way back. A previous version that can be restored on purpose, and someone has restored it on purpose at least once.
- A staging environment that resembles production closely enough that passing there means something.
Only then does urgent patching start, and only then does anything that could be called improvement. This sequence is unglamorous and it is the whole reason a takeover works — every change after it is reversible and observable, which is what makes moving quickly safe rather than reckless.
The four that are almost always wrong
Not a prediction about your system specifically. These are the ones worth checking first because they are cheap to check, they recur across stacks and team sizes, and each one is capable of ending a business on its own.
- Backups exist and have never been restored. Sometimes the job has been failing for months and the failure notification goes to an address nobody reads. The restore is the control; the backup is only the input to it.
- Dependencies are years behind, and at least one of them is a major-version cliff. The cost of staying current is small and continuous. The cost of catching up is a project, and it grows every month it is deferred until it is indistinguishable from a rewrite.
- One person knows how to deploy. Often it is a sequence of steps on their machine, partly documented, partly remembered. This is the risk clients underestimate most, and it is usually the cheapest thing on the entire list to fix.
- There is no staging environment, so production is where changes are tested. Everyone involved knows this is wrong and everyone has stopped mentioning it. It is the reason releases happen at night and the reason they happen rarely.
Two more that turn up nearly as often: secrets committed to the repository at some point in its history and never rotated afterwards, and a domain, DNS or TLS renewal sitting in a personal account belonging to somebody who left.
What this looks like as an engagement
Audit, then risk report, then stabilise, then operate — in that order, with a decision point after the risk report where either side can stop. The audit is the part that costs you nothing, and it is deliberately structured so that the document is useful even if you take it to a different supplier. How a takeover works →