How to maintain a uv project
A uv project needs regular care, like a dog. Most of that care can run on autopilot: Dependabot for lockfile bumps, uv audit in CI for vulnerability scans, and a scheduled workflow for prek auto-update. What’s left is a short workstation check on the first Monday of the month.
The rest of this guide gives the GitHub Actions configs for the three automated jobs, walks through the manual monthly checks, and closes with the one rule that holds the whole thing together: don’t bundle a maintenance bump into a release.
Tip
All three configs come pre-wired in the uv-project-care GitHub template. Click Use this template to scaffold a new repo with Dependabot, uv audit, and the scheduled prek auto-update workflow already in place.
Automate the recurring work
These three jobs cover the recurring care. Set them up once per project, then forget them.
Automate lockfile updates with Dependabot
The manual command is uv lock --upgrade, which re-resolves every dependency to the newest version that fits the constraints in pyproject.toml:
$ uv lock --upgrade
Resolved 14 packages in 111ms
Updated requests v2.31.0 -> v2.33.1
To upgrade one package without disturbing the rest:
$ uv lock --upgrade-package requests
Run that on a calendar if you must. The cleaner pattern is letting Dependabot do it for you.
Dependabot supports uv as of March 2025. Add .github/dependabot.yml:
version: 2
updates:
- package-ecosystem: "uv"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 5Dependabot opens scheduled PRs that update uv.lock and any matching pinned versions in pyproject.toml. CI runs the test suite against each PR; merging the green ones is the entire workflow.
Note
Dependabot does not relax range constraints. If the project pins requests>=2.31.0,<2.33 and 2.33.1 is the latest, Dependabot won’t propose widening that ceiling. Loosen the constraint manually first, then let Dependabot keep the lock current.
If a release branch needs to stay frozen on the existing lock, exclude it from Dependabot via target-branch: main and let main carry the bumps.
Run uv audit in CI
The manual command is uv audit, which checks every package in the lockfile against the OSV vulnerability database:
$ uv audit
Resolved 14 packages in 3ms
Found 3 known vulnerabilities and no adverse project statuses in 13 packages
requests 2.31.0 has 3 known vulnerabilities:
- GHSA-9hjg-9r4m-mvj7: Requests vulnerable to .netrc credentials leak via malicious URLs
Fixed in: 2.32.4
The cleaner pattern is running it in CI on every push, with a workflow that fails the build on findings. The full setup is in how to scan Python dependencies for vulnerabilities. Wire that workflow up once and every push gets scanned.
Important
uv audit is gated behind a preview-features warning in current uv releases. Pass --preview-features audit to silence it, or accept the warning until the command stabilizes.
Schedule prek auto-update weekly
The manual command is uvx prek auto-update, which trims every rev: in .pre-commit-config.yaml up to the latest release tag:
$ uvx prek auto-update
https://github.com/astral-sh/ruff-pre-commit
would update rev `v0.6.0` -> `v0.15.12`
(Use pre-commit autoupdate if the project uses pre-commit instead of prek; the two are command-line compatible apart from prek’s hyphenated subcommand name.)
The cleaner pattern is a scheduled workflow that runs autoupdate weekly and opens a PR with the diff. This one uses peter-evans/create-pull-request to handle the commit and PR creation:
name: Autoupdate pre-commit hooks
on:
schedule:
- cron: "0 9 * * 1"
workflow_dispatch:
permissions:
contents: write
pull-requests: write
jobs:
autoupdate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: astral-sh/[email protected]
- run: uvx prek auto-update
- uses: peter-evans/create-pull-request@v8
with:
title: "chore: autoupdate pre-commit hooks"
commit-message: "chore: autoupdate pre-commit hooks"
branch: chore/autoupdate-hooks
delete-branch: trueMonday 9am UTC, weekly. The PR is empty when nothing changed and gets merged with one click otherwise. For tighter supply-chain protection, pin the action versions to a full SHA instead of a tag.
Important
The workflow’s permissions: block is necessary but not sufficient on a fresh repo. Also enable Settings → Actions → General → Workflow permissions → “Allow GitHub Actions to create and approve pull requests”. Without that toggle, peter-evans/create-pull-request fails with GitHub Actions is not permitted to create or approve pull requests.
Handle what’s left manually each month
Once the three CI jobs are running, this is what’s left for a human. Block time on the first Monday of the month; if nothing’s broken it’s five minutes.
Merge the bot PRs
Open the repo and burn down the queue:
- Dependabot’s stack of
uv.lockbumps. - The autoupdate-hooks PR from last Monday.
- Any other dependency PRs that have accumulated.
Each one already has a green test run. Squash-merge the green ones; investigate the red ones.
Review the audit ignore list
Every --ignore in your audit configuration is a deliberate exception. Once a month, walk the list and check which exceptions still apply and which now have an upstream fix you can pull in.
$ uv audit --ignore GHSA-9hjg-9r4m-mvj7
--ignore-until-fixed <ID> is a softer form that automatically un-ignores once the advisory lists a fixed version. Convert long-standing ignores to that form when you can.
Update uv and Python on your workstation
CI runners are ephemeral; they install fresh uv and Python on every run. Only the workstation needs these:
$ uv self update
$ uv python upgrade
The full mechanics, including how virtual environments transparently follow patch upgrades, are in how to upgrade uv and how to keep Python up to date with uv python upgrade.
Prune the patch directories that have accumulated since last month:
$ uv python list --only-installed
$ uv python uninstall 3.11.8 3.12.3
Each retained patch is roughly 100 MB. Two years of unmanaged upgrades are a noticeable disk hit on a laptop.
Don’t bundle maintenance with releases
A release should pin to a known-good uv.lock that the test suite and CI have already exercised. Maintenance days bump the lock forward; release days don’t. The two belong on different days, on purpose.
Schedule maintenance for the Monday after a release, not the Friday before. Mid-cycle Dependabot PRs get tested and age before they ship; bumps that arrive the day before a release wait for the next cycle.
Related
- How to use a uv lockfile for reproducible Python environments
- How to scan Python dependencies for vulnerabilities
- How to upgrade uv
- How to keep Python up to date with uv python upgrade
- How to set up prek hooks for a Python project
- Setting up GitHub Actions with uv
- How to pin GitHub Actions by SHA for Python projects