The Cache Key Trick That Makes uvx Actually Fast in GitHub Actions
Simon Willison found a clean way to make `uvx tool-name` reproducible and cache-friendly in CI: pin resolution to a date, then use that date as your cache-busting lever.
By TRAGenX Desk
If you've wired uvx (uv's pipx run equivalent) into a GitHub Actions workflow, you've probably hit the annoying tradeoff at the center of Simon Willison's latest TIL: uvx ruff or uvx some-tool happily resolves to whatever the newest published version is, which is great for freshness and terrible for caching. Every run is a fresh resolution, which means a fresh PyPI round-trip, which is exactly what you're trying to avoid by caching in the first place.
The trick: pin resolution to a date, then cache on that date
uv ships an environment variable, UV_EXCLUDE_NEWER, that constrains dependency resolution to package versions published on or before a given date. Set it once at the top of the workflow, and every uvx tool-name call in that job resolves to "the newest version as of that date" instead of "the newest version, period." That single change turns a non-deterministic command into a deterministic one — and a deterministic command is something you can safely key a cache on.
Willison's recipe folds that same date string into the GitHub Actions cache key:
env:
UV_EXCLUDE_NEWER: "2026-07-12"
steps:
- uses: actions/cache@v4
with:
path: ~/.cache/uv
key: uv-${{ runner.os }}-${{ env.UV_EXCLUDE_NEWER }}
- run: uvx tool-name --versionBecause the resolution is pinned, the cache hit is guaranteed to contain exactly what uvx would have resolved anyway — no drift between what's cached and what's requested. To upgrade, you don't touch a lockfile or chase a version bump: you just move the date forward. That busts the old cache key, uv re-resolves against the new cutoff, and the fresh result gets cached under the new key. Willison also notes he'd like setup-uv to default toward keeping cached wheels rather than purging them, which is an open ask on Astral's uv project rather than something solved by this trick alone.
Why this matters beyond one TIL post
This is a small fix, but it's a useful pattern for anyone running AI-assisted or agent-driven pipelines — the kind of CI where a workflow might invoke a dozen small CLI tools (linters, formatters, one-off scripts an agent generated) rather than a handful of pinned dependencies in a lockfile. uvx-style "just run this tool" ergonomics are exactly what make agentic workflows fast to write, but that convenience quietly reintroduces the non-determinism that lockfiles exist to kill. The UV_EXCLUDE_NEWER-as-cache-key pattern gets you both: the zero-setup convenience of uvx tool-name, and the reproducibility and cache economy of a pinned dependency graph — without maintaining an actual lockfile per tool.
It's also a good instance of a broader habit worth stealing: when a tool resolves 'latest' on every invocation, look for a way to turn that resolution into a value you control, then use that same value as your cache key. The date is arbitrary — what matters is that it's a single explicit variable governing both what gets installed and whether the cache is considered valid.
FAQ
Frequently asked questions
- What does UV_EXCLUDE_NEWER actually do?
- It tells uv to ignore any package versions published after the given date when resolving dependencies, so `uvx tool-name` and similar commands resolve deterministically to "newest as of that date" instead of always picking the true latest release.
- How do I upgrade my CI tools with this pattern?
- Bump the `UV_EXCLUDE_NEWER` date in your workflow. Since that date is part of the GitHub Actions cache key, the change automatically invalidates the old cache and triggers a fresh resolution and cache write under the new key.
- Does this only work for uvx?
- The specific environment variable is uv-specific, but the underlying pattern — pin a resolver to an explicit, controllable value and use that same value as your cache key — applies to any tool that otherwise resolves 'latest' non-deterministically on every CI run.
Sources
- TIL: Using uvx in GitHub Actions in a cache-friendly way — Simon Willison
- uv — an extremely fast Python package manager — Astral