How to Use `--exclude-newer` for Reproducible Python Environments
--exclude-newer tells uv to ignore every package artifact uploaded after a cutoff you choose. Resolution sees only what existed at that moment, so it produces the dependency graph that moment would have produced. As a rolling window (a duration like 7 days), the same option becomes a dependency cooldown, a defense against supply chain attacks.
The option takes four kinds of value:
| Value | Example | Effect |
|---|---|---|
| Date or RFC 3339 timestamp | 2023-12-12, 2023-12-12T00:00:00Z |
Pins resolution to a fixed point in the past |
| Friendly duration | 24 hours, 1 week, 30 days |
Trails the current date as a rolling window |
| ISO 8601 duration | PT24H, P7D, P30D |
Trails the current date as a rolling window |
false |
false |
Clears a cutoff inherited from configuration |
The cutoff is compared against the upload time of each individual artifact, not the release date of the version it belongs to.
Tip
For ongoing work, a lockfile reproduces an environment more precisely than any date can. Reach for --exclude-newer when there is no lockfile to restore, when you are bisecting the date a dependency started failing, or when you want the rolling cooldown.
Pin resolution to a fixed date
Pass an RFC 3339 timestamp to uv lock, uv sync, or uv add:
$ uv lock --exclude-newer 2023-12-12T00:00:00Z
Resolved 6 packages in 205ms
uv writes the cutoff it settled on into uv.lock, where you can audit it:
[options]
exclude-newer = "2023-12-12T00:00:00Z"Warning
A cutoff passed on the command line is not saved as project configuration. The next plain uv lock or uv sync re-resolves without it and strips the [options] block from the lockfile. The pinned versions survive as resolution preferences, so nothing looks broken, but the record of the cutoff is gone and the next uv lock --upgrade resolves against every release. Write the value into pyproject.toml for anything beyond a one-off command.
Write a cutoff that means the same thing on every machine
A bare date covers all of that day in the machine’s local timezone, so --exclude-newer 2023-12-12 records 2023-12-13T00:00:00Z on a UTC machine, 2023-12-13T05:00:00Z in New York, and 2023-12-12T15:00:00Z in Tokyo. Fourteen hours separate the Tokyo and New York cutoffs, which is enough to swallow a release, so two colleagues running the same command can lock different versions.
Give the cutoff as a timestamp whenever it names a date. Once a resolution has run, the concrete timestamp lives in uv.lock, so the ambiguity only bites the machine that resolves first.
Reproduce an environment from a specific date
A repository last touched in June 2023 with no lockfile resolves against three years of releases it never saw. Clone it, run uv init --bare, and give uv the date instead:
$ uv add -r requirements.txt --exclude-newer 2023-06-15T00:00:00Z
Resolved 6 packages in 128ms
Installed 5 packages in 4ms
+ certifi==2023.5.7
+ charset-normalizer==3.1.0
+ idna==3.4
+ requests==2.31.0
+ urllib3==2.0.3
The uv pip interface takes the same option, which suits a requirements.txt project that is not ready to adopt pyproject.toml:
$ uv pip compile requirements.in --exclude-newer 2023-12-12T00:00:00Z
Resolved 5 packages in 1ms
# This file was autogenerated by uv via the following command:
# uv pip compile requirements.in --exclude-newer 2023-12-12T00:00:00Z
certifi==2023.11.17
# via requests
charset-normalizer==3.3.2
# via requests
idna==3.6
# via requests
requests==2.31.0
# via -r requirements.in
urllib3==2.1.0
# via requests
uv pip install, uvx, and uv run accept the same flag: uvx --exclude-newer dates the tool it builds, and uv run --exclude-newer dates the dependencies declared in a script’s PEP 723 inline metadata block, the standard that lets a single .py file carry its own requirements. Interpreters stay outside the cutoff: uv python install has no --exclude-newer, so it never restricts which CPython build arrives.
pip has an equivalent, --uploaded-prior-to, which accepts an ISO 8601 datetime or a duration such as P7D but applies per invocation rather than persisting in project configuration.
Roll the cutoff forward with a duration
A duration anchors the cutoff to the present, so it advances as time passes. This is the dependency cooldown pattern: refuse any release that has not survived in public for a set period.
# pyproject.toml
[tool.uv]
exclude-newer = "7 days"How to Protect Against Python Supply Chain Attacks with uv covers choosing the window, setting it machine-wide, and exempting a package that needs an urgent patch. What Is a Dependency Cooldown? explains why uv records the span in uv.lock rather than a timestamp, so the window moves only when a fresh resolution runs.
Apply the cutoff to every command in a project
Add exclude-newer under [tool.uv] so every uv lock and uv sync uses it without anyone passing a flag, and collaborators inherit it when they clone:
# pyproject.toml
[tool.uv]
exclude-newer = "2023-12-12T00:00:00Z"The same key works at the top level of uv.toml. Use one file or the other: a uv.toml beside pyproject.toml makes uv ignore the entire [tool.uv] section, so a cutoff set there stops applying and uv warns that the field was dropped.
For a single shell session or a CI job, set the environment variable instead:
export UV_EXCLUDE_NEWER="2023-12-12T00:00:00Z"Four places can set the cutoff, and the one closest to the command wins: the --exclude-newer flag, then UV_EXCLUDE_NEWER, then project configuration ([tool.uv] or uv.toml), then a user-level ~/.config/uv/uv.toml that applies to any project that sets nothing of its own. Add -v to any resolving command to print a Solving with exclude-newer: line naming the value that took effect; read it first when a resolution surprises you and a cooldown is configured somewhere outside the project.
Both the flag and the environment variable accept false, which clears a cutoff that project or user configuration would otherwise apply:
$ uv lock --exclude-newer false --upgrade
Resolving despite existing lockfile due to removal of global exclude newer
Resolved 6 packages in 90ms
Updated certifi v2023.11.17 -> v2026.7.22
Updated charset-normalizer v3.3.2 -> v3.5.1
Updated idna v3.6 -> v3.19
Updated requests v2.31.0 -> v2.34.2
Updated urllib3 v2.1.0 -> v2.7.0
Override the cutoff for one package
--exclude-newer-package gives a single package its own rule. Set it to false to exempt the package entirely, or pass a date or duration to give it a cutoff of its own:
uv lock --exclude-newer 2023-12-12T00:00:00Z --exclude-newer-package requests=falseEvery other package stays behind the December 2023 cutoff while requests resolves to its newest release:
[[package]]
name = "requests"
version = "2.34.2"
[[package]]
name = "idna"
version = "3.6"The same overrides work in configuration, where they take precedence over the global value:
[tool.uv]
exclude-newer = "7 days"
exclude-newer-package = { requests = false, setuptools = "30 days" }The verbose resolver line lists each rule uv applied, with an exempted package shown as disabled:
$ uv lock --refresh -v
...
DEBUG Solving with exclude-newer: global: 2026-09-03T00:44:51.311065Z, setuptools: 2026-08-11T00:44:51.311067Z, requests: disabled
Exempt an index that does not publish upload times
Dating an artifact requires the upload-time field from PEP 700, which added size and upload-time metadata to the index API. PyPI publishes it. Many private indexes do not, and uv treats an artifact it cannot date as unavailable:
$ uv lock
warning: six-1.17.0-py2.py3-none-any.whl is missing an upload date, but user provided: 2026-07-24T11:32:10Z
× No solution found when resolving dependencies:
╰─▶ Because there are no versions of six and your project depends on six, we
can conclude that your project's requirements are unsatisfiable.
hint: `six` was filtered by `exclude-newer` to only include packages uploaded before 2026-07-24T11:32:10Z. The latest version satisfying the requirement is v1.17.0. Consider using `exclude-newer-package` to override the cutoff for this package.
The hint names the cutoff uv applied and the newest version the requirement would otherwise accept. Set exclude-newer = false inside that index’s block to let its packages through while the rest of the resolution keeps the cutoff:
[tool.uv]
exclude-newer = "7 days"
[[tool.uv.index]]
name = "internal"
url = "https://internal.example.com/simple"
exclude-newer = falseThe index-level exclude-newer field accepts a date or duration if the index should have its own cutoff instead of none.
Note
uv treats exclude-newer on an index as a preview feature and prints a warning on every run. Pass --preview-features index-exclude-newer to silence it.