Skip to main content
All guides

Project rescue

How to audit a codebase you just inherited

An audit is worth doing only if it ends in decisions. This is the order to work in, what each finding actually costs to fix, and how to rank the results.

Updated November 10, 2025 · 8 min read

The short answer

  • Start by building and running the system from a clean checkout on a machine that has never seen it; the time that takes is the single most predictive measure of how expensive every future change will be.
  • Check the deployment path next. A pipeline that anyone on the team can run tells you the code is changeable, and manual file copying tells you it is not, regardless of how the code itself reads.
  • Read the commit history for who worked on the system and whether changes were reviewed, then measure test coverage only on the code that carries money or risk, not as a repository-wide percentage.
  • Scan for dependency age, known vulnerabilities, and credentials committed to the repository; these are cheap to find, and the first two are usually cheap to fix while the third requires rotation and history cleanup.
  • Price every finding before you rank it: a one-week fix that removes a recurring outage outranks a six-month refactor that only improves how the code reads.

You have taken over a codebase. The original team left, or you acquired the company, or the vendor relationship ended badly. Either way you now own something you did not write and cannot yet estimate against. The instinct is to start reading source files, and that is the least efficient thing you can do. A codebase gives up its condition fastest through its build, its deployment path, and its history. What follows is the order to work in and what each finding costs to fix.

Give the work a fixed window. Three to five days of one senior engineer answers the questions below for a mid-sized system.

Can it be built and run from a clean checkout

Take a machine that has never run this system. Clone the repository, follow whatever setup instructions exist, and time yourself to the point where the application responds to a request and a test suite runs. Write down every step you had to work out yourself and every credential someone had to send you.

Under an hour means the team treated onboarding as part of the product. Half a day is normal. Multiple days, or a result that only works because a former employee talked you through an undocumented step, tells you the environment exists in someone's head. That finding predicts everything else: every hire costs a week before producing anything, and nobody will safely reproduce a production bug locally.

How does code reach production

Ask someone to walk you through the last deployment, and watch rather than accept the description. A pipeline that runs on merge, executes tests, and can be triggered by anyone on the team means changes are cheap and reversible. A pipeline only one person runs, or that people routinely bypass, means the control is decorative. Someone copying files onto a server means there is no reliable relationship between what is in source control and what is running.

That last case is common in inherited systems, and you cannot trust the repository as a description of production until you have diffed them. Changes made directly on a server invalidate the rest of the audit if you miss them. While you are there, ask when a deployment was last reverted and how long it took. A team that has never rolled back has either been lucky or has no mechanism.

What the history tells you

Commit history is the cheapest source of organizational truth in the repository. It records what people did rather than what they intended.

  • How many distinct authors, and how commit volume distributes across them
  • Whether changes arrived through reviewed merges or straight onto the main branch, and whether that pattern changed
  • Commit message quality, which tracks whether anyone expected to read the history later
  • Long gaps in activity, which usually mark a handover, a layoff, or a stretch maintained by whoever was available
  • Files that change in almost every commit, which are the parts of the design everything else routes through
  • A single enormous initial-import commit, which usually hides a vendor handoff with no history behind it

That last one matters most. If the history begins with a bulk commit dated eighteen months ago, you have lost the ability to ask why any decision was made. Treat it as a constraint on estimation, not a defect to fix.

Tests, dependencies, and credentials

Coverage where it matters, not coverage as a number

A repository-wide coverage percentage tells you almost nothing, because teams under pressure reach a target by testing whatever is easy to test. Pick the three or four operations that carry money or regulatory risk (pricing, invoicing, eligibility, permissions, payroll) and check whether those paths have tests, whether the tests assert on outcomes rather than on the mocks they set up, and whether the suite currently passes. A suite that has been failing for months is worse than no suite, because it trains everyone to ignore a signal. Time the run as well: a forty-minute suite gets skipped, and a flaky one gets re-run until it passes.

Dependency age and known vulnerabilities

Run the ecosystem's audit tooling and record two things: how far behind the runtime and framework sit relative to supported versions, and which dependencies carry published advisories. Age matters more than count. Two minor versions behind is routine maintenance. A runtime past end of support is a hard deadline you have inherited, because security fixes have stopped and upgrading is a project rather than a patch.

Credentials committed to the repository

Search the repository and its history for committed secrets: database passwords, API keys, private certificates, connection strings with embedded credentials. Standard scanning tools do this well. Treat anything found as exposed regardless of whether the repository is private, because it has been on every laptop that ever cloned it. Remediation is rotation first, then removing the value from history, then a pre-commit scan. Rotation is the part people skip, and the only part that reduces risk.

The data model, error handling, and observability

The schema is the truest description of the domain in any system. Application code can be replaced; the data model is what the business has been recording for years. Read it before the services. Look for status fields with values nobody can enumerate, columns named after a customer, nullable foreign keys that imply an optional relationship someone later made mandatory in code, and tables that are two concepts merged under delivery pressure. Each is a business rule that was never written down, and each carries migration cost.

Then look at what happens when things go wrong. Find the places where errors are caught and silently discarded, because those are why problems get reported by customers rather than by monitoring. Establish whether centralized error reporting exists, whether anyone receives the alerts, and whether logs are retained long enough to investigate an incident that surfaces a week later. A system with no observability is not necessarily broken, but it cannot be operated with confidence.

Concentration risk

For the parts of the system that matter most, check how many people have ever committed to them. Code exactly one person has touched is a dependency on that person, and in an inherited codebase that person has usually already left. Where they are gone, assume any estimate touching that area carries a wide range. Where they are still available, their time belongs in knowledge transfer rather than new features.

Turning findings into a priced list

A useful audit attaches a cost and a consequence to each item. Nobody can act on a list of things that are wrong. They can act on a list ranked by what fixing them buys.

Audit areaWhat to look forSeverity signal
Clean-checkout buildTime from clone to a running application and a passing test run on an unfamiliar machineCritical past a day, or where a step is known only to someone who has left
Deployment pathWhether a pipeline exists, who can run it, whether it is bypassed, when a rollback last happenedCritical if files are copied to a server by hand, or the deployed artifact does not match source control
Commit historyAuthor distribution, reviewed merges versus direct pushes, message quality, activity gapsHigh and permanent where one dominant author has left, or history starts with a bulk import
Tests on money pathsWhether pricing, invoicing, permissions, and payroll logic have passing tests that assert on outcomesHigh for a suite red for months; low coverage on low-risk code is noise
Dependency ageDistance from supported versions for the runtime, framework, and major librariesHigh with a fixed deadline once past end of support; minor versions behind is routine
Known vulnerabilitiesPublished advisories against current versions, and whether any are reachable from user inputHigh on internet-facing components; transitive advisories in build tooling are usually medium
Committed credentialsPasswords, API keys, certificates, and connection strings in files or in historyCritical until rotated, regardless of repository visibility
Data modelUndocumented status values, merged concepts, customer-specific columns, inconsistent keysHigh on any entity a replacement must migrate, since ambiguity converts directly into effort
Error handling and observabilitySilently swallowed errors, no centralized reporting, unowned alerts, short log retentionHigh when incidents are discovered by customers rather than by the system
Concentration riskModules with exactly one lifetime contributor, weighted by how business-critical they areCritical where that contributor has left and the module carries revenue or compliance logic
Audit areas, what to look for, and how to read the severity

Cost is what makes the ranking defensible. A working setup script is one to three days and pays back on every future hire. A basic deployment pipeline where none exists is one to two weeks. Rotating exposed credentials is hours of engineering plus coordination with whoever owns the downstream systems. Upgrading a runtime past end of support is weeks to months. Restructuring a data model that merged two concepts is a migration project measured in months, and it should almost never be first.

That arithmetic reorders the list in a way that surprises people. The findings that read as most alarming in a technical review are often the slow, expensive ones, while the items that most reduce risk this quarter are unglamorous: a setup script, a pipeline, rotated keys, and error reporting that reaches a human.

  1. 1

    Agree the window and the questions first, and state that the audit ends in a ranked list with costs attached.

  2. 2

    Get read access to source control, the deployment mechanism, a production-like database copy, and monitoring on day one.

  3. 3

    Build and run from a clean checkout on an unfamiliar machine, logging every step and every blocker verbatim.

  4. 4

    Confirm what is deployed matches source control, then trace the deployment and rollback path with the person who performs it.

  5. 5

    Read the commit history for authorship, review practice, and gaps before forming any opinion about code quality.

  6. 6

    Run the test suite and the dependency audit, and scan the repository and its history for committed credentials.

  7. 7

    Read the schema, then trace two or three business rules that carry money and confirm them against real data.

  8. 8

    Attach effort and consequence to every finding, rank by risk removed per week, and present the top five.

Common questions

How long should an audit of an inherited codebase take?
Three to five days of one senior engineer is enough to answer the questions above for a mid-sized system, and one to two weeks for a large or multi-service estate. Past two weeks you have stopped assessing and started rewriting. Bound the window in advance and accept that some areas will be sampled rather than covered completely.
Should we audit before or after taking over maintenance?
Before, if the original team is still reachable, because their availability is the scarcest input and it disappears on a known date. If you have already taken over, run the audit in the first two weeks anyway, ahead of committing to any delivery dates. Estimating against a system you have not assessed is how a handover turns into a missed first commitment.
What do we do about a finding we cannot afford to fix?
Record it with its cost, its consequence, and the condition that would force the decision, such as an end-of-support date, a compliance deadline, or the departure of the one person who understands it. Accepting a risk deliberately is a legitimate outcome and is very different from not knowing about it. Revisit the register when one of the named conditions fires.
Does a low test coverage number mean the codebase is bad?
Not on its own. Coverage measures which lines executed during a test run, not whether anything meaningful was asserted, and teams reach a target by testing the code that is easiest to test. Look instead at whether the operations carrying money or regulatory risk have tests that assert on outcomes and currently pass. A system at thirty percent coverage concentrated on billing logic is in better shape than one at eighty percent that avoids it.
Can the audit be run by the team that will maintain the system?
Yes, and there is an advantage to it, because the engineers doing the setup and reading the history are the ones who will carry the knowledge forward. The two things internal teams struggle with are estimating work in an unfamiliar stack and reporting findings that reflect badly on colleagues or on a recent acquisition decision. If either applies, keep the inventory internal and bring an outside reviewer in for the cost estimates and the ranking.
What should the audit produce as a document?
A setup log that works as onboarding documentation, a ranked findings list with an effort estimate and a consequence per item, and a proposed first increment of no more than five items. Anything longer stops being read. Current-state diagrams and maturity scores are optional; the ranked list with costs is the part that lets someone approve work.

Want this applied to your operation?

The first conversation is a no-cost fit discussion about the problem, its importance, and the people involved. We respond within one business day.

Start a conversation