Engineering
Keeping an Old Project Alive With Small, Surgical Updates

Part 2 of the thread Dev habits for a team of one
- Before touching an old project, get a baseline: what's installed, what's vulnerable, and whether the tests pass today.
- Change one thing at a time, test after each, and make sure undo is a single command.
- Database migrations are the riskiest change in the pile. Back up first, every time.
- When Claude does the update, make it show the plan and the blast radius before it edits anything.
There's a note in my vault called Project Maintenance Updates, and it reads like it was written for a company with a change board. Canary releases from 5% to 25% to 100%. Approval workflows. Team notification cascades. An audit logger. Health monitoring that rolls back on its own.
All of that exists for good reasons. But a side project with one maintainer doesn't have a change board, and it doesn't need one. The idea underneath the note still holds, though: update like a surgeon, not like a demolition crew. Here's that version, cut down for a team of one.
Get a baseline first
The riskiest update is the one you start without knowing where you stand. Three questions before anything changes:
- What's out of date?
pip list --outdatedlists every installed package with a newer version available. - What's actually vulnerable?
pip-auditchecks installed packages, or arequirements.txtwith-r, against the Python Packaging Advisory Database. "Outdated" and "vulnerable" are very different lists, and the second one is usually short. - Do the tests pass right now? Run them before you touch anything. If they're already failing, you need to know that, or you'll blame the update for something that was broken last spring.
pip list --outdated
pip-audit -r requirements.txt
pytest
git switch -c update/requests
That last line is the cheapest insurance there is: a branch. If it all goes sideways, main never knew.
One change at a time
The note's prompt asks for "intelligent dependency analysis" and "breaking change detection." For one person, that mostly means reading the changelog before bumping a major version, and changing one thing at a time.
| Change | Risk | How I'd handle it |
|---|---|---|
| Patch release (1.4.2 to 1.4.3) | Low | Batch a few, run the tests |
| Minor release (1.4 to 1.5) | Medium | One at a time, skim the release notes |
| Major release (1.x to 2.0) | High | Its own branch, read the migration guide first |
| Python version | High | Its own branch, and CI on both versions for a while |
| Database migration | Highest | Back up, test on a copy, have a way back |
The one-at-a-time part matters because when something breaks, you want exactly one suspect. Bump eight packages at once and a failing test could belong to any of them.
Warning
My notes call database migrations the highest-risk update, and I agree. Code rolls back with
git revert. Data doesn't. Take a backup you've actually tested restoring, and run the migration on a copy first.
Make undo boring
The note promises rollback "under 5 minutes." I'd rather not promise a number. Just make undo a single, dull step:
- Code: tag the last good version before you start, so going back is a checkout, not an archaeology dig.
- Dependencies: pin exact versions in a lock or requirements file, and commit it. Then "go back" means installing the old file.
- Data: a backup from right before the change, not last week's.
And write down what you changed and why, even if it's one line in a changelog. Future-you is the one who'll read it.
Letting Claude do the surgery
This is where the idea pays off most. Asking an AI agent to "update the dependencies" invites it to change everything at once and tidy up a few things while it's in there. The surgical version is a tighter brief:
Update only [package] from [old version] to [new version] in this project.
Before editing anything:
- Read the changelog between those versions and list the breaking changes that affect this code.
- List every file you expect to change, and why.
- Tell me which tests cover the affected code.
Then stop and wait for my go-ahead. After the change, run the full test suite and report the results. Don't touch anything else, even if it looks untidy.
That "don't touch anything else" line does a lot of work. So does the pause before editing. It's the same rule I use when I'm running three Claude Code agents: nothing is done until it's done and tested. It also belongs in the project's CLAUDE.md, so every session starts with it.
If the old project never got a proper test suite or linterA tool that reads your code without running it and flags mistakes and sloppy habits, like an unused import or a variable that's never set.More: Scaffolding a Python Project So Future-You Doesn't Hate It in the first place, the scaffolding post covers what to add. Adding tests before updating anything is dull, and it's the reason the update goes well.
- A DevOps Reference for a Team of OneEngineering
- Turning Ubuntu Server Into a Light XFCE DesktopResources & Recommendations
- Scaffolding a Python Project So Future-You Doesn't Hate ItEngineering