actions/setup-python installs a CPython, PyPy or GraalPy version, puts it on PATH, and can cache pip, pipenv or poetry dependencies keyed on your dependency files. In v7.1.0, the current release, its cache keys contain the full installed Python version and, since v6.3.0, the Linux distribution, so a change to either starts the cache cold.

Every segment of the key is a way to miss. Only pip has a prefix fallback, and cache-hit reports true only on an exact match.
A setup-python step is usually two lines, python-version and cache: pip, and both hide decisions. The version you write is a range the action resolves against what the runner already has, and the cache key is built from the result rather than from what you wrote.
That is how a cache goes cold with no change to your workflow: the runner image ships a new Python patch, the key changes with it, and the next run installs from scratch. v6.3.0 did the same thing once to every Linux cache when it added the distribution to the key.
Inputs, and the version actually installed
The table is action.yml at the v7.1.0 tag. Three outputs come back: python-version, the version actually installed, which is what you want when the input was a range; python-path, the interpreter; and cache-hit. With update-environment left on, the action also updates PATH, PKG_CONFIG_PATH and pythonLocation; turn it off in a composite action that should not touch its caller's environment, and call python-path directly.
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
id: py
with:
python-version-file: .python-version
cache: pip
- run: pip install -r requirements.txt
- run: echo "Installed ${{ steps.py.outputs.python-version }}"
| Input | Default | What it does |
|---|---|---|
python-version |
Read from .python-version
|
Exact version, range, x.y-dev, pypy3.10 or graalpy-24.0
|
python-version-file |
None |
.python-version, pyproject.toml, .tool-versions or Pipfile
|
cache |
None |
pip, pipenv or poetry
|
architecture |
The host's |
x86, x64 or arm64
|
check-latest |
false |
Download a newer patch than the cached one when it exists |
token |
github.token on github.com |
Fetches builds from actions/python-versions; never sent to a custom mirror |
mirror |
actions/python-versions on raw.githubusercontent.com |
New in v7.1.0: another source for CPython builds |
mirror-token |
None | New in v7.1.0: sent verbatim as the Authorization header |
cache-dependency-path |
Per package manager, see below | Files, globs or a list to hash for the key |
update-environment |
true |
Set pythonLocation and PATH for later steps |
allow-prereleases |
false |
Fall back to a prerelease when no GA version matches an x.y range |
freethreaded |
false |
Install the free-threaded build |
pip-version |
None | Pin pip to major[.minor][.patch]; CPython only |
Inputs and outputs from actions/setup-python action.yml at tag v7.1.0 (commit 6d4fd22b20465ccc0f7354476588e8df7c9ca0e0), read 2026-11-12.
Choosing the version
If you set both inputs, the action warns "Both python-version and python-version-file inputs are specified, only python-version will be used." With neither, it reads .python-version from the repository, and failing that uses the Python already on PATH with a warning. The advanced usage guide is blunt that the PATH version varies between runners and can change unexpectedly.
The shape of the version decides the speed. A runner image keeps one patch of each supported minor, so 3.13 resolves to whatever patch is installed and sets up without a download. An exact 3.12.6 is reproducible, but if the image has moved on, the action downloads it, and on Windows that means an installer. That choice also decides your cache key, as the caching section shows.
x.y-dev takes the newest build of that minor including alphas, betas and release candidates, and an exact prerelease tag such as 3.14.0-alpha.1 installs that build. Semver ranges like >=3.9 <3.14 and 3.x work too.
Prereleases and free-threaded builds
allow-prereleases: true lets an x.y range fall back to a prerelease when no GA release of that minor exists yet, which keeps one matrix entry valid before and after the release. For CPython it applies only to the x.y form.
Free-threaded Python exists from 3.13. Ask for it with a t suffix, as in 3.13t, 3.13.1t or 3.14t-dev, or with freethreaded: true. The suffix is not semver, so a range such as >=3.13 needs the input instead. The python-version output carries the t, so the cache key of a free-threaded leg never collides with the standard build.
strategy:
fail-fast: false
matrix:
python: ['3.13', '3.14', '3.14t']
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
with:
python-version: ${{ matrix.python }}
allow-prereleases: true
cache: pip
- run: python -m pip install -r requirements.txt && python -m pytest
Caching for pip, pipenv and poetry
The key is setup-python-<RUNNER_OS>-<arch>-<distro version>-<distro>-python-<installed version>-<manager>-<hash>, so an illustrative pip key on Ubuntu 24.04 reads setup-python-Linux-x64-24.04-Ubuntu-python-3.13.9-pip- followed by the hash. The version segment is the resolved version, patch included. With python-version: '3.13', the day the runner image moves to a new patch every key changes and every cache misses once. Pinning the patch keeps the key stable, at the cost of a download whenever the image has moved past it.
pip saves the pip cache directory and hashes **/requirements.txt, falling back to **/pyproject.toml. It is the only mode with a prefix fallback, and cache-hit reports false when that fallback restored. pipenv saves the virtualenvs directory and hashes **/Pipfile.lock; poetry saves each project's virtualenvs path, plus .venv for in-project environments, and hashes **/poetry.lock. Both are exact-match only. All three save in a post step that runs only on success.
Poetry has to be on PATH before this step, because the action asks it for its configuration, and after restoring it runs poetry env use with the new interpreter. Whether cache: pip is worth anything on your project is measured on the pip and poetry caching page.
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- run: pipx install poetry
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
with:
python-version: '3.13'
cache: poetry
- run: poetry install
- run: poetry run pytest
Keys and paths from src/cache-distributions/ at setup-python v7.1.0 (pip-cache.ts, pipenv-cache.ts, poetry-cache.ts, cache-distributor.ts), read 2026-11-12. 3.13.9 is an example patch, not a claim about any runner image.
Majors that moved the cache key
Two of these changes moved cache keys: v6.3.0 added the distribution segment, so the upgrade itself started every Linux cache cold once. The pip-install input lived for one minor release before v7 removed it.
| Version | Released | What changed |
|---|---|---|
| v5.0.0 | 2023-12-06 | Runtime moved from node16 to node20 |
| v6.0.0 | 2025-09-04 | Runtime moved to node24 (runner v2.327.1 or later); pip-version input; versions read from Pipfile; a missing cache directory became a warning |
| v6.1.0 | 2025-11-25 |
pip-install input; GraalPy early-access and Windows builds |
| v6.3.0 | 2026-06-24 | RHEL support; Linux distribution added to cache keys |
| v7.0.0 | 2026-07-20 | ESM internals; pip-install removed; the versions manifest is validated and its fetch retried |
| v7.1.0 | 2026-10-22 |
mirror and mirror-token inputs; clearer error messages |
From the actions/setup-python release notes, read 2026-11-12.
Versions not found and dependency files missing
There is no dedicated Learn page for these yet, so the fixes are here. Network failures pip hits after the step, in the install itself, are on pip read timeouts and 429s in CI.
| Message | Cause | Fix |
|---|---|---|
The version '3.8' with architecture 'x64' was not found for Ubuntu 24.04. |
No build in the versions manifest for that version, OS and architecture, or before v7.0.0 a manifest fetch that failed quietly | Check the manifest URL the error prints; move to v7 for the retry |
The specified python version file at: <path> doesn't exist. |
The path is wrong, or checkout has not run | Fix the path relative to the workspace |
No file in <dir> matched to [**/requirements.txt or **/pyproject.toml] for pip. |
cache is set and no dependency file exists, often in a reusable workflow |
Set cache-dependency-path, or drop cache
|
Unable to locate executable file: poetry. |
cache: poetry ran before Poetry was installed |
Install Poetry in an earlier step |
Invalid pip-version "<value>". Please specify a version in the format major[.minor][.patch]. |
pip-version is not a plain version |
Use 25, 25.1 or 25.0.1
|
FAQ
What does "version with architecture x64 was not found" mean in setup-python?
The action found no build for that version, operating system and architecture in the actions/python-versions manifest, the same failure older releases worded as "Version X with arch x64 not found". In issue 1319 it was a temporary fetch problem rather than a missing build, which is why v7.0.0 validates the manifest and retries the fetch. If it persists, the build does not exist for that runner image.
Does cache: pip cache the installed packages?
No. It restores pip's own cache directory, so pip install still runs and still installs every package; it downloads and builds less. Issue 330 asking for the installed packages to be cached is still open; the poetry and pipenv modes are the ones that save an environment.
Why does poetry install use the wrong Python version after setup-python?
Poetry creates environments with the Python it was installed with, and the documented order installs Poetry before setup-python (issue 846). With cache: poetry the action runs poetry env use against the new interpreter after restoring; without it, run poetry env use "${{ steps.py.outputs.python-path }}" yourself before poetry install.
What happened to the pip-install input?
It arrived in v6.1.0 and was removed in v7.0.0, after issue 1251 pointed out that it installed packages into the runner-level Python environment, which a virtual environment may not see. Replace it with an ordinary run: python -m pip install ... step, or a virtual environment, after setup-python.
Top comments (0)