uv-override-prune
Detect prunable override-dependencies / constraint-dependencies entries in uv projects.
Install
Requires uv on PATH, since the audit runs uv lock.
uv tool install uv-override-prune
Or run it without installing — useful for one-off checks:
uvx uv-override-prune
Usage
# Detect prunable entries (default)
uv-override-prune # checks ./pyproject.toml
uv-override-prune path/to/pyproject.toml # checks given file
# Remove prunable entries in place
uv-override-prune --fix
Example output:
=== override-dependencies (3 entries) ===
[KEEP] aiohttp>=3.13.5 3.13.3
[PRUNE] httpx>=0.1.0 0.28.1
[SKIP] foo==1.0 (non-lower-bound)
Run with --fix to prune entries marked [PRUNE].
Exit codes:
| Code | Meaning |
|---|---|
0 |
No prunable entries (or --fix succeeded) |
1 |
Prunable entries found (without --fix) |
2 |
pyproject.toml not found or malformed, or an entry marked [ERROR] (the audit is incomplete, even when other entries were pruned) |
Why
uv lets you pin a transitive dependency version via [tool.uv] override-dependencies and constraint-dependencies.
A common reason to reach for these is CVE mitigation: a vulnerability is disclosed in a transitive package, and you force the patched minimum version while direct deps catch up.
Once they do, the entry is no longer doing anything — but it's easy to forget which ones are still load-bearing. Stale overrides become a judgment cost at every audit or upgrade ("is this still needed, or just history?").
uv-override-prune answers that mechanically: it checks whether each entry's lower bound is already satisfied by what uv lock would resolve without the override, starting from the versions in your current uv.lock.
How it works
For each candidate entry, the tool removes it in a temp copy of pyproject.toml and uv.lock, runs uv lock there, and checks whether the resulting natural resolution still satisfies the entry's specifier. If yes, the entry is [PRUNE].
Starting from the existing uv.lock matters because uv lock keeps already-locked versions wherever it can. A from-scratch resolution would upgrade every package first, which can make an entry look redundant even though removing it in the real project would downgrade the package. To evaluate against the newest versions instead, run uv lock --upgrade before the tool. Without a uv.lock, the tool falls back to a from-scratch resolution and says so on stderr.
Scope
- Targets entries in
[tool.uv] override-dependenciesandconstraint-dependencies. - Only specifiers using
>=and/or>are checked. Entries using==,~=,<,<=,!=(alone or mixed) are skipped. - Entries with an environment marker (e.g.
foo>=1.0; python_version >= "3.10") are skipped, since the naturaluv lockresolution doesn't reflect the marker's intent. - When uv forked the resolution on environment markers and locked several versions of the package (one per fork), the entry is
[PRUNE]only if every version satisfies it.[KEEP]lists the versions below the bound;[PRUNE]lists them all. - An entry that repeats an earlier entry in the same section (same package and specifier, ignoring spelling) is reported as
[PRUNE]with(duplicate). The first copy is evaluated with every copy removed, so its verdict reflects the natural resolution and--fixleaves exactly one copy.
Known limitations
- The temp copy holds only
pyproject.tomlanduv.lock, so metadata that needs a build to read is out of reach. A project whosedependencies,optional-dependencies, orrequires-pythonare declareddynamichas every entry[SKIP]ped as(dynamic-metadata). A dynamicversionis fine (the copy pins a placeholder), and[tool.uv.sources]path deps, workspace members, and[project] readmeare rewritten to absolute paths. - One-at-a-time evaluation: if overrides interact (e.g. cascade redundancy, shared transitive deps), individual runs may miss some prunable entries. Re-run after applying removals to surface the next layer.
License
MIT
Metadata
Release files for uv-override-prune 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| uv_override_prune-1.0.0.tar.gz | 24.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| uv_override_prune-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 38.1 kB
Release files / uv_override_prune-1.0.0.tar.gz
| Download URL | uv_override_prune-1.0.0.tar.gz |
|---|---|
| Size | 24.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
35694b8555e45f2989435190c33586531a09ad6ec807b58e15b7ab3eb512fb11
|
|
BLAKE2b-256 checksum How to use checksums |
8e03070264885904d62aad68182582d447d2cdc6b655f0b31b1a171f6f849450
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.17 {"installer":{"name":"uv","version":"0.12.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / uv_override_prune-1.0.0-py3-none-any.whl
| Download URL | uv_override_prune-1.0.0-py3-none-any.whl |
|---|---|
| Size | 13.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
141314ed0f319575479cba1328479cf904e9f9def6e70c4a61d9016bbc193cfd
|
|
BLAKE2b-256 checksum How to use checksums |
cbaa499fe8befd27ef1564c1d0da667f5773547425efbd2724c3fad7d76af9d5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.17 {"installer":{"name":"uv","version":"0.12.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|