tox-gh
Seamless integration of tox into GitHub Actions.
tox-gh automatically selects which tox environments to run on each Python version in your GitHub Actions matrix, allowing parallel test execution across multiple workers.
Tutorial: Getting Started
This guide walks you through setting up tox-gh for the first time.
Prerequisites
You'll need a Python project already using tox 4 with a tox.toml, tox.ini, or pyproject.toml configuration file.
Step 1: Add tox-gh configuration
Open your tox configuration file and add a [gh] section that maps Python versions to tox environments.
For tox.toml:
[gh.python]
"3.13" = ["py313"]
"3.12" = ["py312"]
For tox.ini:
[gh]
python =
3.13 = py313
3.12 = py312
This tells tox-gh: "When running on Python 3.13, run the py313 environment. When running on Python 3.12, run the
py312 environment."
Note: In
tox.ini, the[gh] pythondict entries must use=as the key-value separator (not:). Using:will cause parsing to fail silently since:is an INI key-value delimiter.
Step 2: Create a GitHub Actions workflow
Create .github/workflows/test.yaml:
name: test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.13', '3.12']
steps:
- uses: actions/checkout@v5
- name: Install uv
uses: astral-sh/setup-uv@v7
- name: Install tox
run: |
uv tool install --python ${{ matrix.python-version }} tox --with tox-gh
- name: Run tests
run: tox
Step 3: Push and verify
Commit your changes and push to GitHub. The workflow will run automatically. You'll see:
- Two parallel jobs (one for Python 3.13, one for 3.12)
- Each job runs only its corresponding tox environment
- GitHub Actions log groups organize output by environment
You've successfully integrated tox-gh. The plugin now handles environment selection automatically based on which Python version GitHub Actions provides.
How-to Guides
How to run multiple environments on a single Python version
Add a list of environments for a Python version:
[gh.python]
"3.13" = ["py313", "type", "lint"]
In tox.ini:
[gh]
python =
3.13 = py313, type, lint
Now the Python 3.13 job will run three tox environments sequentially: py313, type, and lint.
When using factored environments (e.g., py313-django{50,51}), list the full env names since tox-gh replaces the
envlist rather than filtering it:
[gh.python]
"3.13" = ["py313-django{50,51,52}"]
"3.12" = ["py312-django{50,51,52}"]
In tox.ini:
[gh]
python =
3.13 = py313-django{50,51,52}
3.12 = py312-django{50,51,52}
How to test freethreaded Python builds
Freethreaded Python (3.13t, 3.14t) is automatically detected. Map it like any other version:
[gh.python]
"3.14t" = ["py314t"]
"3.14" = ["py314", "type"]
In tox.ini:
[gh]
python =
3.14t = py314t
3.14 = py314, type
The plugin checks for the freethreaded attribute and generates the correct version key with the t suffix.
How to explicitly set the Python version
If automatic detection doesn't work for your setup, set TOX_GH_MAJOR_MINOR:
- name: Run tests
run: tox
env:
TOX_GH_MAJOR_MINOR: ${{ matrix.python-version }}
This forces tox-gh to use the matrix value instead of detecting the Python version.
How to bypass tox-gh environment selection
Run tox with an explicit environment list:
tox -e py313,lint
Or set the TOXENV environment variable:
env:
TOXENV: py313,lint
When you specify environments explicitly, tox-gh respects your choice and skips its own selection logic.
How to use with uv
Install both tox and tox-gh as uv tools:
- name: Install tox
run: uv tool install tox --with tox-uv --with tox-gh
The --with flag ensures tox-gh is available when tox runs.
Reference
Configuration
[gh.python]
Maps Python version strings to lists of tox environments.
Type: dict[str, list[str]]
Location: The configuration can be placed in tox.ini under [gh] python = ..., in tox.toml under [gh.python],
or in pyproject.toml under [tool.tox.gh.python].
Format (tox.toml):
[gh.python]
"<version>" = ["<env>", ...]
Format (tox.ini):
[gh]
python =
<version> = <env>, ...
Important: In
tox.ini, dict entries underpythonmust use=as the separator. Do not use:— it is treated as an INI key-value delimiter and will silently break the mapping.
Format (pyproject.toml):
[tool.tox.gh.python]
"<version>" = ["<env>", ...]
Version keys (in priority order): Freethreaded Python versions use "3.14t" or "3.13t". Standard CPython versions
use "3.14", "3.13", "3.12", "3.11", or "3.10". Major-only fallbacks use "3". PyPy versions use
"pypy-3.13", "pypy-3", or "pypy3". Pyston versions use "piston-3.13" or "pyston-3".
Behavior: The matched environment list replaces the tox env_list entirely. It does not filter the existing
envlist. When using factored environments, you must specify the full env names (generative syntax like {50,51} is
supported).
Examples (tox.toml):
# Run one environment per version
[gh.python]
"3.13" = ["py313"]
# Run multiple environments on one version
[gh.python]
"3.13" = ["py313", "type", "lint"]
# Freethreaded Python
[gh.python]
"3.14t" = ["py314t"]
"3.14" = ["py314"]
# Fallback to major version
[gh.python]
"3" = ["py3"]
Examples (tox.ini):
# Run one environment per version
[gh]
python =
3.13 = py313
# Run multiple environments on one version
# python =
# 3.13 = py313, type, lint
# Freethreaded Python
# python =
# 3.14t = py314t
# 3.14 = py314
# Fallback to major version
# python =
# 3 = py3
Environment Variables
GITHUB_ACTIONS
Required: Must be "true" for the plugin to activate.
Automatically set by GitHub Actions. The plugin does nothing when running locally.
TOX_GH_MAJOR_MINOR
Optional: Override automatic Python version detection.
Values: Any string matching a key in [gh.python] (e.g., "3.13", "3.14t")
Use cases: This variable is useful when using uv python install instead of setup-python, for freethreaded builds
that need explicit version specification, or when automatic detection fails.
Example:
env:
TOX_GH_MAJOR_MINOR: ${{ matrix.python-version }}
TOXENV
Optional: Explicitly specify which tox environments to run.
When set, tox-gh is completely bypassed and tox runs the specified environments.
Example:
env:
TOXENV: py313,lint
Python Version Detection
The plugin detects the Python version by first checking the TOX_GH_MAJOR_MINOR environment variable (highest
priority). If not set, it uses virtualenv.discovery.py_info.PythonInfo.from_exe() introspection to check the
free_threaded attribute (appending a t suffix if true), check the implementation (handling PyPy, Pyston, or
CPython), and extract major and minor version numbers.
The detection returns a prioritized list of version keys to try. For freethreaded CPython 3.13, it returns
["3.13t", "3.13", "3"]. For standard CPython 3.13, it returns ["3.13", "3"]. For PyPy 3.13, it returns
["pypy-3.13", "pypy-3", "pypy3"].
The plugin uses the first matching key found in your [gh.python] configuration.
GitHub Actions Integration
Log Grouping
The plugin creates collapsible log groups using GitHub Actions commands. The ::group::tox:install group contains the
package installation phase. Each environment's test run gets its own ::group::tox:{env-name} group. The ::endgroup::
command closes the current group. This organization makes long CI logs more readable by sectioning output.
Step Summary
When running multiple environments (2 or more), the plugin writes to $GITHUB_STEP_SUMMARY. Environment passes are
marked with ✓ :white_check_mark:: env-name and failures with ✗ :negative_squared_cross_mark:: env-name.
Single-environment runs don't produce summary output.
Behavior
Activation Conditions
The plugin activates when GITHUB_ACTIONS=true, no explicit -e flag was passed to tox, and the TOXENV environment
variable is not set. Otherwise it remains inactive.
Environment Selection
When active, the plugin first detects the Python version and returns a prioritized key list. It looks up the first
matching key in the [gh.python] mapping, inserts the matching environments into tox's env_list via MemoryLoader, and
sets the TOX_GH_MAJOR_MINOR environment variable (if not already set) for subprocess inheritance.
Subprocess Provisioning
When tox provisions itself (via requires in tox config), the matched version key is propagated to the subprocess via
TOX_GH_MAJOR_MINOR. This ensures provisioned tox processes apply the same environment filtering.
Explanation
Why tox-gh exists
GitHub Actions workflows typically define a matrix strategy with multiple Python versions:
strategy:
matrix:
python-version: ['3.13', '3.12', '3.11']
Each matrix cell spawns a separate worker. Without tox-gh, you can either run all tox environments on every worker
(which is wasteful and redundant) or manually specify which environments to run via TOXENV (which is verbose and
error-prone). tox-gh eliminates both problems by automatically mapping Python versions to appropriate tox environments
based on simple configuration.
How environment selection works
The plugin hooks into tox's configuration system via tox_add_core_config. When tox starts, it checks if running in
GitHub Actions (GITHUB_ACTIONS=true), verifies the user hasn't specified explicit environments (respecting -e and
TOXENV), detects the current Python version via virtualenv's introspection, matches the version against the
[gh.python] mapping, and overrides tox's env_list with the matched environments. This happens before tox creates any
environments, so tox never sees the unfiltered list.
Design decisions
Why not use tox factors?
Tox 4's native platform factors (linux, darwin, win32) work well for OS-based selection. However, Python version
factors still require explicitly setting which environments to run. tox-gh provides automatic version-based selection
without workflow boilerplate.
Why detect Python version instead of reading workflow matrix?
The workflow matrix is not accessible from inside the tox process. GitHub Actions only exposes it via environment variables you explicitly pass. Auto-detection means zero required environment variables in simple cases.
Why propagate version key to subprocesses?
When tox provisions itself (installs dependencies into an isolated environment and re-invokes), the MemoryLoader
override exists only in the parent process. Without propagation, the child tox process would run all environments.
Setting TOX_GH_MAJOR_MINOR in os.environ makes it inherit automatically.
Comparison to tox-gh-actions
tox-gh-actions offers more configuration flexibility ([gh-actions:env] for arbitrary matrix variables) but requires
tox 3 or 4. tox-gh is simpler (Python version mapping only) and requires tox 4+. For most projects, Python version
mapping is sufficient since tox 4's factor conditions handle other use cases natively.
Limitations
The plugin only supports Python version-based environment selection and requires tox 4.31 or higher. It does not support
arbitrary workflow matrix variables beyond Python versions. Version detection may fail with unusual Python
installations, in which case you should use the TOX_GH_MAJOR_MINOR override.
Release files for tox-gh 1.8.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| tox_gh-1.8.0.tar.gz | 11.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| tox_gh-1.8.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 22.0 kB
Release files / tox_gh-1.8.0.tar.gz
| Download URL | tox_gh-1.8.0.tar.gz |
|---|---|
| Size | 11.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
19c151525e6ed383b2fd23f8f3a35301ee12f3438c19ac7062f81c30958626ad
|
|
BLAKE2b-256 checksum How to use checksums |
a0ba5cac156bf0488be76488008dc9b82d0210845e8e60074af0fd6717955c01
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.13
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 21, 2026.
Transparency logRelease files / tox_gh-1.8.0-py3-none-any.whl
| Download URL | tox_gh-1.8.0-py3-none-any.whl |
|---|---|
| Size | 10.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c29a109dc4c1a51bc472b4989bd7063ac9be07bf0c16a2b0fcbe57f16757e397
|
|
BLAKE2b-256 checksum How to use checksums |
0c492dfeb72781e46d5e3f2bc374999bd695515c9015862a53252e59906aafaf
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.13
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 21, 2026.
Transparency log