How to Cache uvx Tools in GitHub Actions
A workflow that runs uvx some-tool fetches that tool from PyPI on every run. setup-uv’s cache is built for projects with a lockfile, so it does not help a job that only runs standalone tools. This guide keys the cache to a pinned date instead, so uvx tools restore from cache with no pyproject.toml or uv.lock in the repository.
If your job installs a project (uv sync against a committed lockfile), you want the lockfile-keyed cache in Setting up GitHub Actions with uv instead. This guide covers the other case: running tools like ruff or sqlite-utils with no project to key on.
Why the default cache misses uvx tools
enable-cache: true invalidates the cache when a dependency file changes. The files it watches are fixed:
**/*requirements*.txt
**/*requirements*.in
**/*constraints*.txt
**/*constraints*.in
**/pyproject.toml
**/uv.lock
**/*.py.lockA tools-only job has none of them, so the key never changes and the restored cache is empty. setup-uv compounds the problem by pruning pre-built wheels after each run, keeping only source-built wheels. Since uvx tools arrive almost entirely as pre-built wheels, the default prune discards exactly what you wanted to keep.
Add the caching workflow
This workflow caches every uvx tool it runs, keyed to a single date. Copy it into .github/workflows/tools.yml:
name: Run tools
on:
workflow_dispatch:
env:
# Pins every uvx tool to versions published on or before this date,
# and drives the cache key below. Bump it to refresh both.
UV_EXCLUDE_NEWER: "2026-07-12"
jobs:
tools:
runs-on: ubuntu-latest
steps:
- name: Install uv and restore the tool cache
id: setup-uv
uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
with:
enable-cache: true
cache-dependency-glob: "" # ignore lockfiles; there are none
cache-suffix: "tools-${{ env.UV_EXCLUDE_NEWER }}" # key the cache on the pin date
prune-cache: false # keep pre-built wheels
- name: Run cached-only on a cache hit
if: steps.setup-uv.outputs.cache-hit == 'true'
run: echo "UV_OFFLINE=1" >> "$GITHUB_ENV"
- name: Run the tools
run: |
uvx sqlite-utils --version
uvx ruff --versionThe action is pinned to a commit SHA with the version in a trailing comment, the pattern from how to pin GitHub Actions by SHA for Python projects. Check the setup-uv releases page for the current SHA before committing.
How the date pins tools and keys the cache
UV_EXCLUDE_NEWER is the environment-variable form of uv’s --exclude-newer flag, covered in how to use --exclude-newer for reproducible Python environments. It tells uv to ignore any package version published after 2026-07-12, so each uvx call resolves to the same versions on every run.
cache-dependency-glob: "" switches off the default lockfile keying so the cache no longer looks for files that do not exist. cache-suffix: "tools-${{ env.UV_EXCLUDE_NEWER }}" puts the date into the cache key directly, so the key changes only when the date does. prune-cache: false keeps the downloaded wheels in the cache instead of stripping them after the run.
Fail the build when a tool is not cached
The second step exports UV_OFFLINE=1 whenever the cache was restored (steps.setup-uv.outputs.cache-hit == 'true'). On a cache hit, uv runs offline: a tool that was already cached runs from disk, and a tool that was not cached fails at once instead of quietly downloading from PyPI.
That failure is the signal you want. When you add a new uvx line, the first run misses the cache, downloads the tool, and stores it. If a later run still cannot find it offline, the cache key and the pinned tools have drifted apart, and the build tells you so rather than hiding a slow, uncached install.
Refresh the tools
Change one line to pick up newer tool versions:
env:
UV_EXCLUDE_NEWER: "2026-08-15"The new date re-pins every tool to versions published on or before it and changes cache-suffix, which invalidates the old cache. The next run downloads the updated tools once and caches them under the new key.
Learn More
- setup-uv caching documentation
- uv caching concepts
- Using uv in GitHub Actions (official guide)
- Caching uvx tools in GitHub Actions, the origin of this pattern