# actions/setup-python: version ranges, cache keys that go cold, and free-threaded builds

> actions/setup-python keys its cache on the exact Python patch. Learn version ranges, pip, pipenv and poetry caching, and free-threaded builds.

Source: https://latchkey.dev/learn/actions/setup-python  
Updated: 2026-11-12

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

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.

```.github/workflows/ci.yml
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.

```.github/workflows/test.yml
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](/learn/speed/cache-pip-and-poetry-in-github-actions).

```.github/workflows/ci.yml
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](/learn/failures/pip-read-timeout-and-429-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.

## References

- [actions/setup-python action.yml at v7.1.0 (read 2026-11-12)](https://github.com/actions/setup-python/blob/v7.1.0/action.yml)
- [actions/setup-python advanced usage](https://github.com/actions/setup-python/blob/v7.1.0/docs/advanced-usage.md)
- [actions/setup-python releases](https://github.com/actions/setup-python/releases)
- [actions/setup-python issue 1319: versions start to get not found](https://github.com/actions/setup-python/issues/1319)
- [Python docs: free-threaded CPython](https://docs.python.org/3/howto/free-threading-python.html)

---

Latchkey runs CI/CD that repairs its own failures. Agent entry points: https://latchkey.dev/agent.txt, https://latchkey.dev/openapi.json, https://latchkey.dev/llms.txt
