Upgrading Dependencies With LLMs in October 2026: Paste the Real Changelog, Move One Major Version at a Time, and Let the Tests Decide
Upgrading dependencies is the chore everyone postpones until a security advisory or a broken build forces the issue. Then you are three major versions behind, the changelog is forty screens long, and the error messages point at code you did not write. A large language model can genuinely help here: it reads release notes faster than you, it is good at mapping "old API call" to "new API call," and it can draft the mechanical edits across a codebase. It can also confidently invent a migration step for a version that does not exist, or "fix" a failing test by deleting the assertion.
This guide is a practical workflow for using an LLM during dependency upgrades without trusting it more than it deserves. The short version: give the model the real changelog instead of asking it to remember one, move one major version at a time, and let your test suite, not the model, decide when you are done.
Every dependency sits on others. Pull one block out carelessly and the whole stack can wobble. Image: Jenga tower 1.JPG by Joshjrowe, CC BY-SA 3.0, via Wikimedia Commons.
Why models struggle with upgrades in particular
Most coding questions are about stable knowledge: how a loop works, what a SQL join does. Upgrades are the opposite. They are about exactly what changed between two specific versions of one specific package, which is the kind of detail a model is most likely to get wrong. Three problems come up over and over:
- Training cutoffs. The model may simply not know about the release you are moving to. Ask it about a version newer than its training data and it will often describe a plausible-sounding release that is really a blend of older ones.
- Version blending. Even for releases it has seen, a model can mix up which change landed in which version, and suggest an API that was added later or removed earlier than you think.
- Silent behavior changes. The hardest breaking changes are not renamed functions; they are changed defaults, stricter validation, or different timezone and encoding handling. These do not cause import errors, so neither you nor the model notices them without tests.
The workflow below is designed around those weaknesses.
Step 1: Get an honest inventory before you ask anything
Start with facts from your package manager, not from the model. Every major ecosystem can tell you what is outdated:
# JavaScript / TypeScript
npm outdated
# Python (inside your virtual environment)
pip list --outdated
# Ruby
bundle outdated
# Go
go list -m -u all
Save that output to a file. It tells you the current version, the newest version allowed by your version ranges, and the latest published version. The gap between those columns is the real size of the job. If you use Dependabot or Renovate, their open pull requests are another accurate inventory.
Now sort the list into three piles:
- Patch and minor bumps that should be backward compatible under semantic versioning. Batch these together.
- Major bumps of small, leaf libraries that only a few files import.
- Major bumps of frameworks and core libraries: your web framework, ORM, test runner, build tool, or language runtime. Each of these gets its own branch and its own pull request.
You can paste the outdated list into the model and ask it to help with this sorting, but check its work against the version numbers yourself. Semantic versioning is a convention, not a guarantee, and some projects make breaking changes in minor releases. Their own release notes will usually say so.
Step 2: Paste the real changelog, not the package name
This is the single most important habit. Do not ask "what changed in version 5 of this library?" Instead, open the project's actual changelog, release notes, or migration guide for the exact versions you are crossing, and paste the relevant sections into the conversation. Then ask the model to work only from that text.
A prompt that works well:
I am upgrading <package> from 3.4.2 to 4.0.1 in a Python 3.12 project.
Below are the official release notes for 4.0.0 and 4.0.1, copied
from the project's changelog.
Using ONLY these notes:
1. List every breaking change that could affect application code.
2. For each one, give a grep or search pattern I can run to find
affected call sites in my repo.
3. Quote the sentence from the notes that supports each item.
If something is not covered by the notes, say so instead of guessing.
<paste release notes here>
Asking the model to quote its source does two things. It makes invented items easy to spot, because there is no sentence to quote. And it gives you a checklist you can review in the pull request later. Search patterns are especially useful: instead of the model guessing where you call a deprecated function, you run the search and get the real list.
If the release notes are huge, split them by version and handle one version's notes at a time. Long pastes are where models start skimming.
Step 3: Move one major version at a time
When you are several majors behind, it is tempting to jump straight to the latest. Resist that. Migration guides are almost always written for one step: "upgrading from 3 to 4," not "upgrading from 1 to 4." Deprecation warnings in version N tell you exactly what will break in N+1, but only if you actually run version N.
A safe rhythm for a big jump looks like this:
- Upgrade to the latest release of your current major version first. Run the tests and read every deprecation warning.
- Fix the deprecation warnings. This is great work to hand an LLM: paste the warning text and the surrounding code, and ask for the replacement the warning recommends.
- Bump to the next major version. Repeat.
To make deprecation warnings visible, turn them up during the upgrade. In Python, for example, you can run your tests with warnings promoted to errors so nothing scrolls past unnoticed:
python -W error::DeprecationWarning -m pytest
In Node.js, node --trace-deprecation prints a stack trace for each deprecation so you can find the calling line. Many test runners and frameworks have their own switch for this; check their documentation.
Good upgrades work like a bridge replacement: build the new span step by step while the old one keeps carrying traffic. Image: Invercoe Bridge replacement by Glen Wallace, CC BY-SA 2.0, via Wikimedia Commons.
Step 4: Let the model do the mechanical edits, in small diffs
Once you have a verified list of breaking changes and the call sites from your searches, the model is good at the repetitive part: renaming imports, updating changed function signatures, swapping a removed option for its replacement. Keep each request small and concrete:
- Give it one file, or one breaking change across a handful of files, at a time.
- Include the quoted changelog line for the change you want applied, so the edit is anchored to the real rule.
- Ask for a diff or for search-and-replace blocks rather than a rewritten file, so you can review exactly what moved.
- Tell it not to change anything unrelated. Models like to "tidy up" nearby code during an upgrade, and those extra edits make review much harder.
For large, purely mechanical renames, an official codemod beats both you and the model. Many big frameworks ship one with a major release. If the migration guide mentions a codemod, run it first and use the LLM only for what it leaves behind.
Step 5: Let the tests decide, and watch for cheating
After each step, reinstall from a clean state and run the full suite. Use the lockfile-respecting install so you test exactly what you will ship:
# JavaScript: install exactly what package-lock.json says
npm ci
npm test
# Python with a pinned requirements file
pip install -r requirements.txt
pytest
When something fails, paste the full error and the relevant code back to the model, just as you would for any bug. But watch the shape of its fixes. During upgrades, models are especially prone to making a test pass instead of making the code correct:
- Loosening an assertion or deleting it.
- Adding a broad try/except or a type-ignore comment around the failing line.
- Pinning the dependency back to the old version "for compatibility."
- Mocking out the exact library you are trying to upgrade.
Any of these might occasionally be the right call, but none of them should slip into a pull request without you choosing it on purpose. A useful instruction is: "If you think the test itself is wrong, say so and explain why before changing it."
Remember that tests only catch what they cover. Before you merge a framework upgrade, also run the app and click through the paths that matter most: sign-in, checkout, file upload, whatever your users depend on. Changed defaults often show up there first.
Step 6: Check the lockfile and the dependency tree
An upgrade changes more than the lines you touched. Your lockfile now pulls in new transitive dependencies, and those deserve a glance too:
- Review the lockfile diff in the pull request. A one-line version bump that adds dozens of new packages is worth a second look.
- Check for duplicate copies of the same library at different versions with
npm ls <package>, orpip checkfor broken requirements in Python. - Run your security scanner after the upgrade, such as
npm auditorpip-audit, to confirm you fixed the advisories that triggered the work and did not add new ones. - Never let the model invent a package name to "replace" a removed dependency. Confirm any new package exists on the official registry, is the one the migration guide recommends, and is maintained. Typo-squatted names on public registries are a real attack route.
Step 7: Write the pull request so a reviewer can trust it
The model can draft a good pull request description if you give it the inputs: the version range, the list of breaking changes you handled (with their changelog quotes), the commands you ran, and anything you deliberately left for later. A reviewer should be able to see at a glance:
- Which package moved from which version to which version.
- Which breaking changes applied to this codebase, and where they were fixed.
- Which changes did not apply, and why.
- How it was tested, including any manual checks.
Keep framework upgrades in their own pull requests, separate from feature work. If something breaks in production, you want to be able to revert the upgrade cleanly without losing anything else.
A quick checklist
- Inventory outdated packages with your package manager, not the model.
- Batch patch and minor bumps; give each framework major its own branch.
- Paste the official release notes for the exact versions and make the model quote them.
- Step through major versions one at a time, fixing deprecation warnings along the way.
- Use official codemods first, then the model for small, reviewable diffs.
- Install from the lockfile, run the full test suite, and reject fixes that weaken tests.
- Review the lockfile diff, check for duplicates, and run a security audit.
- Document the upgrade so it can be reviewed and reverted on its own.
Used this way, an LLM turns the worst part of maintenance, reading long changelogs and making the same edit in forty places, into something you can finish in an afternoon. The source of truth stays where it belongs: the project's own release notes, your package manager, and your tests.
Comments
Post a Comment