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.

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"

A bare date such as 2023-12-12 also works, and it resolves to a different instant on every machine.

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

Passing the bare date 2023-12-12 records a cutoff a day later than the date requested, because a bare date covers all of that day in the machine’s local timezone. The same command produces a different cutoff depending on where it runs:

Machine timezone --exclude-newer 2023-12-12 resolves to
UTC 2023-12-13T00:00:00Z
America/New_York 2023-12-13T05:00:00Z
Asia/Tokyo 2023-12-12T15:00:00Z

Fourteen hours separate the Tokyo and New York cutoffs, which is enough to swallow a release. Two colleagues running the same command can lock different versions.

The timestamp form avoids this, so 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. Give uv the date instead:

git clone https://github.com/example/ml-model
cd ml-model
uv init --bare
uv add -r requirements.txt --exclude-newer 2023-06-15T00:00:00Z
$ 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
uv pip install --exclude-newer 2023-12-12T00:00:00Z requests

Throwaway environments take the same option. uvx dates the tool it builds, and uv run 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:

uvx --exclude-newer 2023-12-12T00:00:00Z --from requests python -c "import requests; print(requests.__version__)"
uv run --exclude-newer 2023-12-12T00:00:00Z check.py

The uvx command prints 2.31.0 rather than the current release, and the script resolves its dependencies against the same cutoff. Interpreters stay outside it: uv python install has no --exclude-newer, so it never restricts which CPython build arrives.

Roll the cutoff forward with a duration

A duration anchors the cutoff to the present instead of to a fixed date, 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"

Passing --exclude-newer "7 days" on the command line resolves the same way once, but does not persist: the next plain uv sync re-resolves with no cutoff at all and drops the span from the lockfile. Keep a cooldown in configuration.

Durations resolve to a fixed number of seconds assuming 24-hour days. Calendar units like months and years are rejected.

uv records the duration rather than the timestamp it resolved to:

[options]
exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values.
exclude-newer-span = "P7D"

Storing the span means the next resolution recomputes the window from that later moment. With the duration in configuration, uv sync installs the versions the lockfile already pins and never recomputes anything, so the window moves only when a fresh resolution runs, such as uv lock --upgrade, uv lock --refresh, or uv add.

Recomputing the window is not the same as taking what it now admits. uv lock --refresh moves the cutoff forward but keeps the locked versions as preferences; uv lock --upgrade is what pulls in releases that have since aged past it.

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:

# pyproject.toml
[tool.uv]
exclude-newer = "2023-12-12T00:00:00Z"
# uv.toml
exclude-newer = "2023-12-12T00:00:00Z"

Use one 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.

Collaborators get the same time-locked resolution when they clone the repository.

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 see which one took effect:

$ uv lock --refresh -v
...
DEBUG Solving with exclude-newer: global: 2023-12-12T00:00:00Z

Reach for that line 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

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:

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"

Give the package a different cutoff by passing a date or duration rather than false:

uv lock --exclude-newer "7 days" --exclude-newer-package requests=2023-12-12T00:00:00Z

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" }

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.

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.

Read the error when the cutoff hides a version

A cutoff that excludes every candidate produces a resolution failure, and uv names the cause in a hint below it:

$ uv lock
  × No solution found when resolving dependencies:
  ╰─▶ Because only requests<=2.31.0 is available and your project depends on
      requests>=2.32, we can conclude that your project's requirements are
      unsatisfiable.

hint: `requests` was filtered by `exclude-newer` to only include packages uploaded before 2023-12-12T00:00:00Z. The latest version satisfying the requirement is v2.34.2, published at 2026-05-14T19:25:26.443Z. Consider using `exclude-newer-package` to override the cutoff for this package.

The hint reports the cutoff uv applied, the newest version the requirement would otherwise accept, and when it was published. Read the requested version constraint against that publication date to decide whether to move the cutoff or exempt the package.

Use pip’s equivalent flag

pip offers --uploaded-prior-to, which accepts an ISO 8601 datetime or a duration in days:

pip install --uploaded-prior-to 2023-06-15 -r requirements.txt
pip install --uploaded-prior-to P7D -r requirements.txt

The two flags differ in scope. --exclude-newer in pyproject.toml persists for every uv lock and uv sync in the project, so collaborators inherit the cutoff. --uploaded-prior-to applies per invocation, so every pip command that needs it has to pass it. Both depend on the index publishing upload-time metadata.

Learn More

Last updated on