Skip to content

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=false

Every 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 = false

The 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.

Last updated on