Code Review

What to Check on a Dependency Bump

The pull request arrives with a one-line description: "Bump lodash from 4.17.20 to 4.17.21." The diff is small, mostly a change in a package manifest and a lockfile. Approving it takes four seconds. That is exactly the problem. A dependency bump is one of the few code changes where the reviewer is asked to certify work they did not do, cannot see, and will not read, on the strength of a version number that moved by a single digit.

This is not a paranoid position. Most bumps are fine. The expensive ones are the ones that look identical to the fine ones, and the cost of a bad bump does not show up in the pull request. It shows up three weeks later, in a production incident whose stack trace points somewhere nobody expected, and the first question in the postmortem is "when did this start?" and the answer is a Tuesday afternoon when someone clicked approve on a patch release.

What follows is a working checklist, the kind built from scars. It is organized around the checks that actually catch bad bumps, not the ones that produce a comforting appearance of diligence.

Read the version number as a promise, then distrust it

Semantic versioning exists to make bumps self-describing. Patch means bug fixes, minor means additions, major means breaking changes. The convention is useful and frequently violated. Plenty of libraries ship breaking changes in minor releases, and a depressing number ship them in patches. A version number is a claim about intent, not a guarantee about behavior, and the reviewer's job is to check the claim against the changelog rather than trust the decimal.

The practical rule: a major bump gets a real review, always. A minor bump gets a skim of the changelog. A patch bump gets the changelog opened for two reasons. The first is that patch releases are where maintainers sneak in the behavioral fix that changes an edge case someone's code depended on. The second is that patch releases are the most commonly auto-merged, which makes them the most commonly unread.

Open the changelog and find the word "breaking"

Every serious library has a changelog, and the changelog is where the truth lives. The check is to scan the release notes between the old version and the new one, not just the newest entry. A bump from 2.3.0 to 2.7.1 spans four releases, and the breaking change is often buried in 2.5.0, not in the patch that triggered the pull request.

What to look for specifically: the words "breaking," "removed," "deprecated," "behavior change," and "now throws." Deprecation is the quiet one. A function that was deprecated two releases ago may be removed in the one being merged, and any code still calling it will fail at runtime rather than at build time if the call site is behind a conditional.

When the changelog is missing or thin, that is itself a signal. A library that does not document its changes is a library whose maintainers do not expect anyone to track them, which means the bump is a gamble. That does not make the bump wrong. It makes it a decision that deserves a sentence in the pull request explaining why the gamble is acceptable.

Check whether the package gained or lost a transitive dependency

The lockfile diff is the most underrated part of a dependency bump. A version bump can quietly pull in a new transitive dependency, and that new dependency arrives with its own install scripts, its own license, and its own supply chain. The size of the install can jump by hundreds of packages for a change that looks like a patch.

Scanning the lockfile diff for added entries catches this. It also catches the opposite problem: a bump that drops a dependency some other part of the project was relying on transitively. That code still works until the day the other package updates and the transitive path disappears, at which point the build breaks for reasons that have nothing to do with the change being made.

There is a grimmer check available for high-stakes bumps. If the package has install scripts (the lifecycle hooks that run on install), read them. A bump that adds a postinstall script is a bump that now executes code on every machine that installs the project. Most are benign build steps. Some are not, and the ones that are not are exactly the ones nobody reads.

Look at who published the release

A release is a claim by a person. For most packages that person is a maintainer who has been publishing for years, and the check is a formality. For others it matters. A release published by an account that has never published before, on a package that has been dormant, with a version that jumped more than the changelog suggests, is the signature of a compromised maintainer account. The pattern has played out enough times that it is worth a glance at the publish metadata before approving a bump on a package nobody has touched in two years.

This check is cheap: the registry shows the publisher and the publish date. The suspicious shape is a sudden release after a long silence, especially if the version bump is larger than the changelog justifies, or if the release adds a dependency where the package had none. A package that previously had zero dependencies and now has one is a package whose attack surface just changed.

Run the tests, but know what the tests do not cover

Green tests are necessary and insufficient. A dependency bump can pass the entire suite and still break production, because the suite tests the project's code paths and the bump changes a behavior the project never explicitly tested. The classic case is a date or number formatting change: the tests assert that a value is formatted, not that it is formatted the way the product expects, and the bump changes the default locale handling or the rounding mode.

The useful question is not "do the tests pass" but "which tests would have caught a behavior change in this dependency." If the answer is "none, because the dependency is only exercised indirectly," the bump deserves a manual check of the one or two places the dependency is actually used. A five-minute exercise of the feature that depends on the bumped package is worth more than a thousand unrelated assertions.

Confirm the bump is targeted, not a drive-by upgrade

A pull request that bumps one package is reviewable. A pull request that bumps one package and regenerates the entire lockfile, updating forty transitive dependencies in the process, is a different animal wearing the same title. The targeted bump has a small, legible diff. The drive-by upgrade has a diff nobody will read, and it ships forty changes under the cover of one.

When the lockfile churns, the question to ask is whether the churn is required. Sometimes it is: the new version genuinely needs a newer transitive dependency, and the lockfile has to move. Often it is not, and the churn is an artifact of running an upgrade command that updates everything it can reach. The second kind of pull request should be split or rejected, because it converts one reviewable change into one unreviewable change.

Note the rollback path before merging

The most practical check on a dependency bump is the one almost nobody performs: what happens if this turns out to be wrong. The rollback path is usually "revert the commit and redeploy," which is fine when the bump is isolated and the lockfile change is small. It is not fine when the bump has already been followed by three commits that depend on the new behavior, or when the lockfile churn makes the revert conflict with everything that landed after it.

Writing the rollback into the pull request description is a small habit with outsized returns. One line, stating which commit reverts cleanly and whether the lockfile can be restored independently, is enough. It forces the reviewer to think about the failure mode at the moment when thinking is cheap, rather than at the moment when the on-call engineer is staring at a graph at 2 a.m. wondering which of yesterday's merges broke it.

A four-second approval is a choice about what failure costs

None of these checks takes more than a few minutes, and most take seconds. The changelog scan is one page. The lockfile diff is a glance at added lines. The publisher check is one field in the registry. The rollback note is one sentence. Together they cost less than the time spent arguing about whether the bump is worth reviewing at all.

The tradeoff is real, and worth stating plainly. A team that reviews every patch bump carefully will move slower on maintenance work, and some of that slowness will be wasted on bumps that were always going to be fine. A team that auto-merges patch bumps will move faster, and occasionally it will absorb an incident whose root cause is a version number nobody read. The question is not which policy is correct in the abstract. It is whether the team has decided, explicitly, which failure it would rather have. Most teams have not decided. They have simply clicked approve, four seconds at a time, and called the accumulation of that a process.

Leave a Reply

Your email address will not be published. Required fields are marked *

Back to top button