The call almost always sounds the same. A system has been running for years, it earns money or holds the business together, and the person who built it is no longer there. Resigned, retired, the agency no longer exists, or the freelancer has not answered for six weeks.
The first question is usually: how long until you understand the code?
That is the wrong first question. Not because it does not matter, but because it describes a problem that can wait. The code keeps running whether anyone understands it or not. What cannot wait are the things outside the code, and in almost every case I have seen, those carry an expiry date nobody knew about.
This article is the order I work in during the first week, and the reasoning behind it. It is deliberately unspectacular. None of it is difficult, and that is exactly why it gets skipped.
The order up front, in case you do not have fifteen minutes right now:
- Secure access and ownership.
- Walk the path to production once.
- Restore a backup once.
- Record the deadlines and the versions out of support.
- Only then work through the code systematically.
The rest of the article explains that order and what regularly goes wrong at each step.
The urgent part is not the code
A software system has two halves, and only one of them sits in the repository. The other is everything needed for the first half to run: the server, access to the server, the domain, the certificate, the database, the backup, the API key at the payment provider, and the path a change travels to reach production.
The source code itself does not expire. Its environment very much does. A Let's Encrypt certificate is gone after ninety days once automatic renewal stops working, and shorter lifetimes are now in circulation too. A domain expires after a year if the invoice goes to a mailbox that no longer exists. An access key stops working when the provider rotates it. A package vanishes from the index, and the build that pulls it goes with it. A server contract ends when the credit card behind it has expired.
Every single one of those takes a running system down without a line of code having changed. And many of them can be defused in the first week with surprisingly little effort, as long as you know about them at all. Some cannot: a domain registered to a former employee can cost weeks. Which is all the more reason to look early.
So the first week is ordered by expiry, not by understanding. Whatever can lapse comes first. Whatever is merely incomprehensible can wait.
Access and ownership, before anything else
The first task is a list, and the list has two columns: where does it live, and in whose name does it run.
The second column matters more, and it is regularly forgotten. Having access and being the owner are not the same thing. I have taken over projects where the company paid for the server but the hosting account ran on a former employee's private email address. Access was there, as long as nobody changed the password. The company was not able to act: it could not cancel the contract, could not move it, and in a dispute could not enforce anything.
The list I put together covers at least these items:
- Repository: where does the source live, who has write access, and does a copy exist outside that service? A repository that lives only at one provider hangs off one account.
- Servers and hosting: which machines are running, with whom, on which contract, and who receives the invoice? Equally important is what should not be running any more: I regularly find paid instances that no longer do anything.
- Domains: which registrar, which expiry date, tied to which account, and is auto-renewal on? Plus the question of who can change the DNS records. That is the switch that redirects a company's entire address.
- Certificates: validity, renewal path, and whether renewal happens automatically or depends on a date in someone's calendar. If it is automatic: where, through what, and what raises an alarm when it fails once?
- Database: where does it live, who has access, and does it sit on the same machine as the application? If it does, that is a separate item for later.
- External services: payment provider, mail delivery, search, maps, inventory. For each of them: which contract, which key, where does the key live, and whose account is behind it?
- Mail: the addresses that receive system alerts and invoices. If one of them belongs to a former employee, the system has a blind spot in exactly the place where it would raise the alarm.
Building this list is not engineering work. It is phone calls, digging through accounting invoices and searching mailboxes. It typically takes one or two days, and it almost always turns up at least one gap nobody would have believed possible beforehand.
What comes next is the one hour that pays for the whole exercise: everything running on a private address gets transferred. Every former employee's access gets revoked, including the ones assumed to be unused. And every account with a payment attached gets an address that somebody will still be reading in two years.
The path to production, walked once by hand
The second question that comes before the code: how does a change reach production, and does that path still work?
This is not rhetorical. On a system without an owner, the last deployment is often a year old. The world underneath has moved on since: the build needs a Node version that no longer exists in the base image. A registry token has expired. A package has vanished from the index. The server refuses the SSH key because the operating system has been updated in the meantime.
A deployment path unused for a year is very likely broken. And you find out at the worst possible moment: when a change is urgent.
So in the first week I deploy once. Not with a change that does anything, but as a controlled no-op: the state already running, rolled out again, or a change with no effect on behaviour. "Just a space" is a poor choice for that, incidentally, as soon as templates, bundled files or checksums are involved. If a staging environment exists, the first run goes there. The point is not the change but the proof that the path is walkable. Whatever breaks, breaks in a situation with no time pressure.
The same exercise applies in reverse. If a change goes wrong, how does the previous state come back, and how long does that take? On an inherited system the answer is alarmingly often: by rebuilding the change backwards. That is not a way back, that is a second change made under pressure.
Whatever comes out of this, I write down. Not as documentation in the grand sense, but as a file that says what I did, in which order, and what I learned doing it. By the end of the week that is the first dependable operating manual this system has ever had, and it exists because somebody walked the path once instead of describing it.
A backup nobody has restored is not a backup
Almost every system I take over has backups. Almost none has a tested one.
That difference only shows up in an emergency, and then it is expensive. A backup has four properties, and three of them usually go unchecked:
- It exists. This one does get checked, usually against a green tick in a dashboard.
- It is complete. The database is commonly backed up. What is not: uploaded files, configuration living outside the repository, and state that only exists in a cache. A database without the documents it points to does not restore a business.
- It lives elsewhere. A backup on the same machine protects against an operator mistake, not against losing the machine, and not against an attacker encrypting everything reachable from that machine.
- It can be restored. This is the only property that counts, and the only one you learn about by trying.
Testing it takes half a day. You take the most recent backup, build an environment from it next to production, and check whether the application starts and whether the data is right. What comes out is two numbers nobody had before: how long a restore actually takes, and which state of the data the backup actually brings back. Those are the actuals. Against them sit the targets, the outage and the data loss your business can just about absorb, which is what RTO and RPO mean. It only gets interesting when you compare the two, and only somebody who has run the test once can do that.
That half day carries a second return, often worth more than the first. Anyone building a running environment out of a backup has to find out what the system needs in order to start: which services, which environment variables, which directories, which version of what. That is precisely the knowledge that left with the developer, and this is a way to get it back without anyone having to tell you.
What expires by itself over the next few months
The fourth item of the first week is a list of dates. It contains everything with a deadline, and it comes from three sources.
Contracts and registrations: domains, certificates, licences, server contracts, maintenance agreements. Each with a date. Anything falling due in the next six months belongs in a person's calendar, not in a file.
Software versions out of support: the language version, the database version, the operating system, the framework. Each of those has a published end of security support, and on a system without an owner at least one of those dates is, in my experience, in the past. That is rarely immediately dangerous and rarely unimportant: without security updates, every future vulnerability becomes one that stays open.
Dependencies with known vulnerabilities: one tool run over the package list shows within minutes which installed packages have reported vulnerabilities. On unmaintained code that list is rarely empty, and it does not answer a question yet, it raises one: which of these packages sit on paths reachable from outside?
What matters is what does not follow from this list. It is not a task list for the first week. It is the basis for deciding what happens over the next three months, and it prevents the two most common wrong conclusions: that everything is equally urgent, and that nothing is.
Only now the code, and even then not as a rebuild
After those four steps the system is one you can act on: you can reach everything, you can deploy, you can go back, and you know what expires when. Now it is worth looking at the code, and now it is also useful, because you can place what you find.
The first look is not reading front to back. On a few hundred thousand lines that would be weeks with no result. What I do instead is three passes with different purposes.
First, the outline. Which entry points exist at all: web requests, scheduled jobs, queues, command line tasks, interfaces for other systems. That list is usually shorter than expected and says more about the system than any diagram, because it can be verified completely. The scheduled jobs matter most here: on inherited systems, cron is where business logic hides that nobody expects to find in the repository.
Second, static analysis. One analyser over the whole codebase, at a low level, with no intention of working through the findings. The purpose is not cleanliness but a map: where do findings cluster, which areas are obviously unfinished, and where does code sit that did not add up even when it was written. Those clusters show where the caution belongs.
Third, the money path. I follow the one flow the business hangs on, from the first request to the database. Order, contract, billing, fulfilment, whichever it is. One path, complete, with everything it touches. After that you do not know the system, but you know the part where a mistake is not discussed, it is paid for.
And only then, before the first real change, comes the safety net. Where no tests exist, I write characterization tests: tests that record what the system does today, explicitly without asking whether that is correct. Such a test is allowed to lock in a wrongly rounded result. Its purpose is not correctness but a statement about whether a change has broken something.
The net does not need to be complete, and attempting that would be the wrong route anyway. It covers the money path from the third pass and the places that will be touched first. Everything else gets added when it has to be.
Why this does not come earlier: a test needs an environment where the system runs, and that environment only comes into being through the restore test. Start with the tests and you end up building that environment on the side, and worse.
The three questions that lived in the developer's head
What is really missing in a handover is not documentation. It is reasoning.
The code shows what was implemented. What it mostly does not show is why it was implemented that way, and whether it was intentional. Why is this figure rounded down here and to the nearest cent there? Why does this reconciliation run at three in the morning and not at four? Why does this query carry an exception for exactly one customer?
Every one of those quirks is one of two things: a deliberate decision with a reason behind it that you would need to know, or an old bug that somebody now relies on. From outside, both look identical. The difference decides whether a change is a fix or an outage.
So in the first week I ask not only about access but about people. Not about developers who are gone, but about those who work with the system: the person in accounting who checks the export every month, the person in sales who knows that for this one customer group you have to save twice. Those people explain in half an hour behaviour you could not place in the code in two days.
What comes out of it is a short list of open questions that I do not resolve but record. "Rounding happens here, reason unknown, affects invoice totals." Such a note is worth more than its length suggests: it stops the next person treating that spot as a bug and fixing it.
A side goal of this week is that it never repeats. A system whose knowledge sits in one head has a bus factor of one, and that is exactly why you are in this situation now. So what is produced in the first week does not belong in my notebook but in the client's repository, next to the code it explains.
What I do not do in the first week
The temptation on an inherited system is strong, and it takes three forms.
No tidying up. In somebody else's code you find something on every page that you would have done differently. Touching it before a safety net exists is the riskiest way to spend time: it changes behaviour without checking behaviour, and when something is then wrong, nobody knows whether the change caused it or it had always been that way.
No version jump. Raising a framework or a language version is the obvious first step and the wrong one. Such a jump changes many places at once, in a system whose current behaviour nobody has recorded yet. The upgrade is right, it just comes later, and it comes in steps that go live one at a time. What that involves I have written up for the jump from PHP 7.4 to PHP 8.4.
No verdict on a rebuild. The question almost always comes up in the first conversation, often with a quote in hand. It is a fair question, but it cannot be answered in the first week. Anyone who cannot put a number on the existing system is comparing a known figure with a feeling, and that comparison always favours the rebuild. Why that goes wrong so often has its own article: why full rewrites fail.
What I hand over at the end of the week is unspectacular instead: a list of access with owners, a deployment path walked once, a tested backup with a real restore time, a list of dates, a map of the codebase and a handful of open questions. None of it looks like progress. Together it is the difference between a system somebody operates and a system that runs as long as nothing happens.
When it is already burning
Everything above describes the orderly case: the system runs, there is just nobody left. Sometimes the trigger is different, and then a shorter version applies.
If the system is currently down, recovery comes before everything else, and the assessment falls out as a by-product: what you had to do to get it running again is your first operating manual. Write it down while you do it, not afterwards.
If there is a suspected breach, you do not simply tidy up. Containment and preserving evidence have to be decided together: depending on the situation it may be necessary to isolate systems or lock accounts immediately, and at the same time the traces must not disappear through careless changes. That is incident response, not an ordinary handover, and it belongs with somebody who does it for a living.
If there is a dispute in the room, for instance with an agency whose contract has not been terminated, the question of who owns what comes before the technology. Source code, licences for bought-in parts, access, data. Settling that in the first week is uncomfortable and cheaper than any later version.
In all three cases the core stays the same: the urgent thing is not understanding the code. The urgent thing is being able to act before the next deadline arrives on its own. How a handover works with me and what the entry point costs is on the page about taking over a PHP project. If you only want an assessment of where you stand, you can get one without a conversation through the Legacy Risk Score.

