Home Features For Vibe Coders For Developers Pricing Documentation Services Contact
← Back to blog

The dependency update problem nobody talks about

Dependabot has been around for years. So has Renovate. Both are fine. Both miss the actual problem.

The actual problem is that nobody knows whether to update.

The patch update lie

react: 18.2.0 -> 18.2.45

Patch update. Safe, right? That’s what semver promises.

Semver is a social contract that nobody enforces. Maintainers ship breaking changes in patch versions all the time, sometimes by accident, sometimes because they don’t think it’s breaking, sometimes because the bug they fixed was relied on by your code.

You can’t trust the version number. You can read the changelog, if there is one. You can read the diff, if you have an hour. Or you can run your tests and ship.

That last one is what most teams do. Hopefully your tests cover the path that broke.

What I do instead

For each dependency update, I want to know three things:

  1. Is this a security update? Update immediately, test in staging.
  2. Is this a feature update I want? Update on a calm Tuesday, test thoroughly.
  3. Is this a “keep current” update? Batch it. Update monthly. Run regressions.

The dependency that doesn’t fit any of those three categories is the dependency I should probably remove.

The transitive dependency horror

You ran pnpm update. You reviewed the lockfile diff. 47 packages changed. You only added one.

Welcome to transitive dependencies. The package you actually wanted to update pulled in updates to its dependencies, which pulled in their dependencies, and now is-array (yes, that’s a real package) is at version 1.0.7 instead of 1.0.6.

Most of the time, this is fine. Sometimes one of those transitive updates breaks something. You’ll spend 4 hours bisecting before you figure out which package in the lockfile diff was the culprit.

I now keep a separate “dependency PR” branch. Update one direct dependency at a time. Run the full test suite. Smoke test. Merge. Move to the next. It’s slower. It catches the breaks before they’re tangled with five other unrelated changes.

The package you forgot you were using

Open your package.json. How many of those packages can you explain why they’re there?

If it’s less than 80%, you have package debt. Some of those packages haven’t been updated in three years. Some of them have known CVEs. Some of them depend on packages that no longer exist, and the only reason your build works is that they’re cached locally.

pnpm why <package> will tell you what’s pulling each transitive in. npx depcheck will tell you which direct dependencies are unused. Both are worth running quarterly.

The one fix nobody implements

Pin your dependencies. Not with ^ or ~. Exact versions. "react": "18.2.0", not "react": "^18.2.0".

This costs you the convenience of pnpm install automatically pulling minor updates. It buys you the certainty that what runs in production is what ran in CI. It buys you reproducible builds across dev machines.

Most teams don’t do this because it feels paranoid. The teams that have been burned by a transitive bump on a Saturday afternoon do it religiously.

What I want from a tool

I want a tool that watches my package.json, tells me when an update is available, classifies it (security/feature/keep-current), and tells me whether anyone else has reported the new version is broken.

Snyk does the security part. Socket does some of the broken-version intelligence. Nothing has put it all in one place.

Until something does, I’d rather update less and read the changelogs.

Join the feed

Get feature releases and engineering updates delivered to your inbox.