DEV Community

Cover image for We've Inherited 40+ Client Codebases in Two Years - Here's the Pattern That Predicts Which Ones Will Break First
Monika Upadhyay
Monika Upadhyay

Posted on

We've Inherited 40+ Client Codebases in Two Years - Here's the Pattern That Predicts Which Ones Will Break First

Every codebase handoff starts with some version of the same thing: a repository link, a short call with whoever maintained it last, and documentation that is either several years out of date or missing altogether. Over the past two years, I’ve been involved in more than 40 such handoffs across different clients, technology stacks, and development teams as part of my work at Mavlers Agency.

After seeing enough of them, I stopped treating each inherited codebase as a completely new mystery. A pattern started to emerge: the codebases that developed serious production problems within the first few months usually had the same gaps—not necessarily poor code, but missing engineering controls around the code.

It's Never the Framework Choice

When you inherit a codebase, it is tempting to start by judging the technology choices: the framework, folder structure, naming conventions, or architectural patterns. In practice, those are poor predictors of what will go wrong next. I’ve seen well-structured applications develop serious production issues and seemingly messy codebases remain stable for years. The more useful question is not whether the code was written the way I would have written it, but whether the engineering systems around that code provide enough visibility and control to make safe changes.

Across the handoffs I’ve worked on, five gaps have consistently stood out as early warning signs.

1. No CI pipeline, or one that's been silently disabled.
This is one of the strongest early warning signs I look for. When deployments are manual-someone connects to a server, pulls the latest code, and restarts the application - there is often no reliable record of what was tested before a change reached production, and no automated gate to catch a bad change. DORA’s delivery research treats change failure rate as a key measure of software delivery performance, and the gap between high - and low -performing teams reinforces how important reliable delivery controls are. In the handoffs I’ve seen, the risk becomes especially obvious when there is no CI pipeline, no meaningful staging environment, and testing depends on someone clicking through the production application after deployment. One inherited codebase had exactly that setup; its first serious outage happened within weeks of the handoff, not because of a change we introduced, but because there was no reliable mechanism for detecting problems before they reached production.

2. Hardcoded credentials still sitting in the repository.
An API key, database password, or service credential committed directly to source control is an obvious security problem, but during a handoff I also treat it as a signal to look more closely at the surrounding engineering controls. If credentials were allowed to remain in the repository, there may be other shortcuts that were never addressed—weak secret management, inconsistent access controls, missing review checks, or configuration that was handled manually. The important distinction is that the credential itself may be fixable in minutes; the real risk is what it tells you about the processes around the code. When we find one, we rotate the exposed credential and then use it as a reason to inspect related areas rather than treating it as an isolated cleanup task.

3. Commit history that has been squashed or starts at the day of handoff.
This is easy to overlook because nothing appears broken: the application runs, the repository is clean, and the current code may even have good test coverage. The problem is that a flattened or missing history removes much of the reasoning behind non-obvious decisions. Months later, someone encounters a strange conditional, an unusual configuration value, or a piece of code that looks redundant, but there is no useful history to explain why it exists. That leaves the team with two bad options: leave code they do not understand untouched, or change it and risk removing a fix that was added for a reason nobody remembers. I’ve encountered this with rate-limiting logic that looked unnecessary during a cleanup but turned out to be protecting a third-party API from excessive requests. The lesson is not that every commit needs to be preserved forever; it is that inherited code needs enough history for the new team to understand the decisions it is taking ownership of.

4. Dependencies with no pinned versions.
Unpinned dependencies create a different kind of handoff risk: everything can appear stable on day one, while the environment gradually changes underneath the application. A new release of a direct or transitive dependency can introduce a breaking behavior weeks or months after the handoff, even when nobody on the new team has intentionally changed the application. This is particularly risky when there is no lockfile, dependency update policy, or automated check for outdated or vulnerable packages. During a handoff, I want to know not only which dependencies the application uses, but whether we can reproduce the same dependency set that was running in production. If we cannot, every future change carries an additional variable that the new team has little control over. Pinning versions and committing the appropriate lockfile is a relatively small step, but it removes a significant source of uncertainty before feature work begins.

5. No documented environment or configuration parity.
One of the more subtle handoff risks is discovering that “it works on staging” does not necessarily mean much. Over time, staging and production can drift apart: different environment variables, service versions, feature flags, infrastructure settings, or even database configurations can leave the two environments behaving differently. If those differences are undocumented, the incoming team may not know which parts of the application are actually being tested before a release. I’ve seen deployments pass every available staging check and still fail in production because a required environment variable existed in one environment but not the other. During a handoff, I therefore want a clear record of the configuration each environment depends on and a reliable way to identify meaningful differences. Whether that is managed through configuration-as-code, automated environment checks, or a maintained configuration checklist matters less than being able to reproduce and verify the conditions under which the application is expected to run.

Why These Five, Specifically

In practice, I’ve started treating the first hour of any handoff as a fixed checklist rather than a general “get familiar with the code” session. I confirm that CI actually runs and gates deployments, look for exposed credentials, check whether the Git history extends far enough to provide useful context, verify that dependency versions can be reproduced, and compare staging and production configuration when both environments are available. These checks do not require deep familiarity with the application’s business logic because they are structural rather than domain-specific. That makes them particularly useful when an existing application is being transferred between teams, including cases where an external web development team takes over a codebase after the original developers have moved on.

Turning It Into an Actual Score

The checklist became more useful once I stopped treating it as a mental note and turned it into something we could compare across handoffs. We now use a simple 0-to-5 handoff-readiness score, with one point for each signal that is actually in place—not simply documented or claimed to exist. A score of 5 means the basic engineering controls are present; 3 or 4 means there are gaps worth addressing before making significant changes; and 0 to 2 is a signal to stabilize the environment before starting feature work. The score is not a measure of code quality. It is a quick way to estimate how much operational uncertainty the incoming team is inheriting and decide what needs attention before that uncertainty turns into a production problem.

The score also changes the conversation with the team handing over the codebase. Instead of relying on a vague assessment such as “the code looks fine” or “there’s some technical debt,” we can point to specific, verifiable gaps: the deployment pipeline is missing, a credential was exposed, useful Git history is unavailable, dependency versions cannot be reliably reproduced, or staging and production differ in ways nobody has documented. That makes the handoff discussion less subjective and gives both teams a concrete starting point. More importantly, it helps separate two questions that are often treated as one: Is the codebase functional today, and is it safe for another team to change tomorrow? A handoff review is primarily concerned with the second question.

What This Changes

None of this replaces a deeper technical audit once the team takes ownership. It changes the order in which that work happens. Instead of spending the first sprint reading through the codebase with no clear indication of where the operational risks are, the incoming team can spend the first hour checking these five signals and use the results to prioritize the audit. A codebase that passes all five checks is not necessarily well-designed, but it is easier to reason about because the basic delivery and runtime controls are visible. A codebase with several gaps needs a different starting point: stabilize the deployment process, secure credentials, make dependencies reproducible, document the environment, and restore enough operational context to make future changes safely.

The most useful lesson from these handoffs has been that the biggest risks are often not visible in the code itself. A codebase can look clean, use a modern framework, and still be difficult to take ownership of if the systems around it are undocumented or unreliable. The five signals give an incoming team a practical starting point: check what controls exist, identify what is missing, and stabilize the gaps before adding new complexity. After enough handoffs, I’ve found that predictability matters more than appearances. You do not need to understand every line of an inherited codebase in the first hour. You need to know whether the environment around that code gives you enough confidence to change it safely.

Top comments (0)