Dependency upgrades are one of the heaviest chores in solo development. There was a stretch where I would open npm outdated at the start of each month, see twenty-plus updates waiting, mutter "next month," and close the tab. Six months later even minor bumps had turned into a weekend-eating compatibility puzzle.
Claude Code made the chore lighter. But early on, I tried to "just delegate the whole thing," and got burned twice. Both times Claude proudly reported "upgrade complete, all tests green," and the next morning a user wrote in: "Images aren't loading." Each time the cause was a different breaking change in an area my tests never touched.
This article is the verification loop I now run, framed around what to delegate to Claude Code and what to keep in human hands so breaking changes do not reach production.
Why the tests passed and production still broke
Looking back at both incidents, they shared two traits. Both were minor version bumps, not majors. And in both cases the test suite was genuinely green. From Claude's vantage, nothing looked wrong.
The first incident was an image-optimization library. A minor release flipped the default behavior of WebP conversion. My tests only used PNG fixtures, so the regression hit production where the user uploads were JPEG-heavy. The second was a client-side form validation library where empty strings shifted from "invalid" to "untouched." My unit tests caught the basic shape, but the E2E flow used a code path that initialized validators differently.
The lesson: breaking changes are not always in the BREAKING CHANGES section of the CHANGELOG. Items labeled "bug fix" frequently behave as a behavioral change for our codebase. Even if Claude Code reads the CHANGELOG, it cannot judge what matters without knowing how we use the library.
Three judgments humans must keep
Before describing the loop itself, here are the calls that should never be delegated.
The selection of what to upgrade. Whether to take only minors, include majors, or extend to lockfile-only churn is a human decision. Hand this to Claude and it tends to propose a sweep of everything, generating a diff too wide to review responsibly.
Identifying "areas where breakage hurts most." Payments, auth, data persistence, image delivery — anything close to the user's core experience needs to be enumerated up front. Claude has no context for this prioritization, so I hand it in explicitly during stage three.
The rollback decision. When verification turns up something suspicious, the choice between "keep digging to fix it" and "back this dependency out" stays with the human. Claude is biased toward "I can fix this," and that bias has cost me whole weekends. I keep an explicit cutoff rule.
With those three locked down, the rest — applying the bumps, running the suite, summarizing CHANGELOGs, drafting fix patches — is fair game for delegation.
The four-stage loop at a glance
The loop I run looks like this:
- Survey — let Claude read the CHANGELOGs and tell me what is likely to affect this repo
- Apply — actually bump versions, run tests and builds, report failures
- Impact scan — re-read the diff against the "breakage hurts most" map
- Go/no-go — a human reads the consolidated log and decides
Stages one through three are Claude-led. Stage four is always human.
Stage 1: Survey — extracting what actually changes
The first move is to gather upgrade candidates with npm outdated (or the equivalent for your stack), then have Claude read each library's CHANGELOG and surface "things that might change behavior in my setup." The trick is to feed our usage context into the prompt so the survey is opinionated.
This repo runs Next.js 16 + Tailwind + tRPC.
I'm planning to bump the dependencies below to their latest minors.
For each one, read the CHANGELOG and list "things that might change
behavior in my stack," with a one-line summary of each change.
Lenses to apply:
- Default value changes (behavior when an option is omitted)
- Return value type/shape changes
- Conditions where exceptions are now thrown or no longer thrown
- File output / network call behavior changes
Candidates:
- next: 16.0.4 → 16.0.7
- @trpc/server: 11.2.1 → 11.3.0
- sharp: 0.34.0 → 0.35.0
...
The point of the prompt is to make Claude re-evaluate items the maintainer marked as non-breaking, through the lens of our codebase. In my experience, the scariest changes hide in the "Bug Fix" section of the CHANGELOG. The output is a per-library list of "0–3 changes worth attention" plus "areas worth special verification." I save this and use it as the input to the next stage.
Stage 2: Apply — let it bump, build, and report
Now Claude actually touches the code. Bump versions, install, run the lint/typecheck/test/build pipeline. I deliberately keep this stage as "do it all in one go, and tell me what broke."
Apply all the dependencies surfaced in stage 1.
1. Update versions in package.json
2. npm install --no-audit --no-fund
3. Run npm run lint, npm run typecheck, npm run test, npm run build in order
4. If anything fails, summarize cause and affected files only.
IMPORTANT: do not fix anything in this pass.
Report shape:
- Failing step:
- Failing tests / files:
- Error message summary:
- Suspected cause (mapped to CHANGELOG entry):
The "do not fix anything" line is essential. Without it, Claude Code will see an error and immediately start patching. I want a clear pause to read the report and decide on a fix strategy first.
Once the strategy is set, I let Claude write fixes — but split into one commit per library, with the library name and the breaking change called out in the message. This makes both review and partial rollback far easier.
Fix the failures below in order. Use one commit per fix and put
the library name plus the change being adapted to in the commit
message:
1. sharp 0.35 — adapt to new WebP default behavior
2. @trpc/server 11.3 — adapt to changed zod input error type
Before changing each file, grep the codebase for the affected
function so we don't miss occurrences.
Stage 3: Impact scan — testing where tests do not reach
This is the stage I added after the two production incidents. A green test suite is not enough to ship. I keep a docs/critical-paths.md file at the repo root that lists "code paths that hurt the most when broken" — payments, auth, image delivery, data persistence. I have Claude re-read it against the upgrade diff.
Read docs/critical-paths.md. For each path listed there,
judge whether the current upgrade diff might affect it.
For each path, output:
- Path name:
- Dependencies touched:
- Suspected affected functions / components:
- Recommended manual verification steps (reproducible locally or in staging):
If Claude flags "the image delivery path may be affected by the sharp WebP default change," I deploy to staging and eyeball-check the path with the JPEG / PNG / HEIC samples real users tend to upload. Five minutes of manual checking would have caught both of my prior incidents.
This stage often surfaces gaps in test coverage. I want next month to need less manual checking, so I have Claude add regression tests on the spot:
For the uncovered areas surfaced in stage 3, list which can be
turned into automated tests and add them in priority order.
Place tests under tests/regression/upgrade/ with the dependency
name in the filename.
Stage 4: Go/no-go — humans read the log
Now the human takes back the wheel. I have Claude consolidate everything from stages 1–3 into a single Markdown — CHANGELOG summary, fix commit list, impact scan output, regression tests added — and I read it before deciding whether to ship.
My hold-and-re-verify rules are simple. Any of these triggers another round:
- Stage 3 flagged impact on payments, auth, or data persistence
- The fix commit count exceeds five (the change surface is too wide)
- Claude's writeup uses "probably" or "likely" more than twice
The last rule is intuitive but reliable: areas where Claude was not confident tend to bite me later. When that happens, I shelve that one dependency to next month and ship the rest.
Once I do ship, I merge the fix commits in order, let staging soak for 24 hours, then promote. The staging soak has caught at least two issues for me — usually cron or async paths that smoke tests do not touch.
Operational rules for keeping the monthly run sane
A loop is only useful if I actually run it. Three rules that keep it on rails:
Calendar-block the first business day of each month. Upgrade work is heavy enough that a fuzzy "I'll get to it" guarantees slippage. A two-hour block, scheduled, defended.
Treat hard-to-read CHANGELOGs as a separate track. Two or three dependencies a year deserve a dedicated session for a major upgrade. The monthly run stays focused on mechanical minors — the work Claude Code is best at.
Save every verification log under docs/upgrade-history/. Next month I feed those logs to Claude as context, so it remembers which dependency surprised us last time. Tiny, but it gives the AI something like institutional memory in a solo project.
Skipping dependency upgrades quietly compounds into technical debt. Full automation is hard, but splitting "what humans decide" from "what AI mechanically does" turns the monthly ritual into a predictable two-hour task. For your next upgrade, start by writing the stage-1 prompt — the rest of the loop will fall into place from there.