codel
← Back to the index

DBG-08 · SEC. 02 Debugging & Evaluation

Dependency Upgrade Breakage Isolation

Pin down which single dependency bump in a multi-package upgrade actually broke the app.

FORMAT
workflow
DIFFICULTY
intermediate
TIME
20 min
TOOLS
claude-code · cursor · codex-cli · gemini-cli
MODELS
any
COPIES
0 so far

When to use this

You ran a bulk dependency update (`npm update`, `bundle update`, `pip install -U`, a Dependabot batch PR) touching a dozen packages, and something broke. You don't know which package caused it and don't want to blame the wrong one from a hunch.

The pattern

Pastes as plain text
A bulk dependency upgrade just broke something. I say what broke at the
bottom of this message.

Do this:

1. Read the lockfile diff yourself (git diff on the lockfile) and list
   every package whose version changed, old -> new. If the lockfile
   change is already committed, diff against the commit before the
   upgrade.
2. Turn what I said broke into a single repro command that exits
   non-zero while broken (a test, a build, a curl check). Show me the
   command. Then revert all the bumps to their old versions and confirm
   the repro command passes at the old versions. This confirms the
   regression is actually in this upgrade and not something else.
3. Reintroduce the version bumps one package at a time, in the order
   they appear in the dependency tree (leaf/low-level packages first,
   since a low-level bump can be the real cause of a higher-level
   package's failure). After each single bump, run the repro command.
4. The moment the repro command fails, stop. That package's version bump
   is the culprit. Do not bump any further packages to "confirm."
5. Look up that package's actual changelog or release notes between the
   old and new version. Quote the specific breaking change that matches
   the observed failure, don't guess which line in the changelog is
   relevant.
6. Report: which package, which version range, which changelog entry,
   and whether the fix should be pinning the old version, upgrading our
   code to match the new API, or filing an upstream issue.

What broke after the upgrade:

Real example output

Lockfile diff after `npm update`: 14 packages bumped, including `zod 3.22.4 -> 3.23.0`, `date-fns 2.30.0 -> 3.6.0`, `react-hook-form 7.45.1 -> 7.52.0`.

Step 2: Reverted all 14, ran `npm run build && npm test`, both pass. Confirmed the regression is in this upgrade.

Step 3: Reintroduced bumps in dependency order. After `date-fns` alone: `npm test` fails, 4 tests in `src/utils/__tests__/formatDate.spec.ts` throw `TypeError: (0, date_fns_1.format) is not a function`.

Step 4: Stopped, `date-fns` is the culprit. (`zod` and `react-hook-form` bumps were never reintroduced, no need to test them.)

Step 5: `date-fns` v3 changelog: "v3 is now published as native ESM, CommonJS `require('date-fns')` default import no longer works the same way, use named imports." Our code does `const dateFns = require('date-fns'); dateFns.format(...)`, which matches this breaking change exactly.

Report: `date-fns` v2 -> v3 broke CommonJS default-import usage. Options: pin `date-fns` to `^2.30.0` for now, or migrate the 6 call sites using `require('date-fns')` to named ESM imports (`import { format } from 'date-fns'`). Recommended pinning short-term since the migration touches 6 files across 2 packages.

Why it works

Bulk upgrades bundle unrelated changes together, so a symptom that "started after npm update" gets blamed on whichever package the developer half-remembers changing, which is often wrong. Reverting everything, confirming the baseline, then reintroducing one change at a time isolates the actual cause instead of the most memorable one, and checking the real changelog turns "this package broke it" into a specific, citable breaking change.

Entry DBG-08 · by codel · 2026-07-09 · CC-BY-4.0