Usage
uv-matrix has two subcommands: run, which executes the expanded jobs, and
list, which prints the selectable jobs without evaluating or running anything.
uv-matrix finds pyproject.toml the same way uv does: it searches the current
directory and walks up through its parents, using the first pyproject.toml it
finds. So you can run it from anywhere inside a project, just like uv —
uv-matrix changes to that project root first, so relative paths in a task’s
run and cwd resolve the same wherever you invoke it. Use --config /
--project (below) to point it somewhere else.
uv-matrix --version # print the version and exit
Commands
uv-matrix run # expand the matrices and run every job
uv-matrix run --task test # run only the "test" task
uv-matrix run --matrix checks # run only the "checks" matrix
uv-matrix run --filter python-version=3.13 # run only cells where python-version is 3.13
uv-matrix run --max-jobs 4 # run up to 4 jobs at once
uv-matrix run --dry-run # print the commands without running them
uv-matrix run --task test -- -k slow # pass args after -- to each job's {{ posargs }}
uv-matrix list # list selectable jobs (no evaluation, no run)
run
Expands every matrix, resolves each (cell, task) pair into a job, and runs the
jobs sequentially. By default each job prints just its name:
==> test:test python-version=3.11
The banner gains detail with -v (see -v, --verbose): -v adds the uv run
command line, and -vv adds the job’s isolated environment directory:
==> test:test python-version=3.11
+ uv run --python 3.11 sh -c pytest
env: .uv-matrix/py3.11-1a2b3c4d
A job whose when expression is false is skipped rather than silently dropped.
The summary always reports how many jobs were skipped, so a when exclusion is
never invisible; pass -v to also name each skipped job:
2 jobs skipped (when)
All jobs passed.
By default the first failing job stops the run; later jobs do not run. When one
or more jobs fail, run lists them and exits 1:
Failed jobs:
- test:test python-version=3.11: exit 1
A job whose continue-on-error is true does not stop the run — the remaining
jobs still run — but its failure still counts: the run exits 1 and the job
appears under Failed jobs:. continue-on-error only changes whether the run
stops, never whether a failure is reflected in the exit code. See
Configuration.
--matrix NAMERun only the matrix with this name. An unknown matrix name is an error.
--task NAMERun only the task with this name. An unknown task name is an error.
--filter KEY=VALUERun only jobs whose matrix cell has
KEY=VALUE. Repeatable: repeating a key ORs its values, different keys AND. An unknown key or value is an error. The value after=may be empty (--filter KEY=) to select cells whose value is the empty string. Combines with--matrixand--task(all must match).--max-jobs NRun up to
Njobs concurrently, overridingmax-jobs. See Parallel execution.--dry-runPrint each job’s command but do not execute it.
-- ARG ...Arguments after
--are exposed to templates as{{ posargs }}and applied to every selected job. See Posargs.
The continue-on-error setting (see Configuration) controls whether run
stops at the first failure or keeps going and reports all failures at the end.
Parallel execution
By default run executes jobs sequentially, inheriting the terminal’s stdio
so each job’s output streams live as it happens. This is the right default for
local runs where you want to watch a single job’s output.
Pass --max-jobs N (or set max-jobs in pyproject.toml; see
Configuration) to run up to N jobs at once:
uv-matrix run --max-jobs 4
--max-jobs overrides max-jobs for that invocation. A value of 1 (the
default) means sequential.
Output in parallel mode
Streaming several jobs’ output to the same terminal at once would interleave
them unreadably, so in parallel mode each job’s output is captured (stdout
and stderr combined) and printed as a single block once the job finishes, under
the same ==> matrix:task … banner used in sequential mode. Each block is
therefore attributable to exactly one job. Sequential mode never captures —
output is inherited and streams live.
Stopping in parallel mode
When a failing job stops the run (its continue-on-error is false, the default),
jobs that have not yet started are cancelled, while jobs already running
are allowed to finish and their output is still reported. The run then exits
non-zero, listing the failures it collected. A failing continue-on-error job
does not cancel anything — the run keeps going (but still exits non-zero).
list
Lists the selectable jobs — matrix name, task, and matrix cell — so you can see
what to target on the command line. It only expands the matrices; it does
not evaluate when, templates, or expressions, and never sets up an
environment or runs anything. (That makes it safe to run against a config you do
not yet trust.)
test:test python-version=3.11
test:test python-version=3.12
checks:lint python-version=3.13
checks:typecheck python-version=3.13
task(positional, optional)Show only the job(s) for this task name.
--filter KEY=VALUEShow only jobs whose matrix cell has
KEY=VALUE. Repeatable: repeating a key ORs its values, different keys AND. An unknown key or value is an error. The value after=may be empty (--filter KEY=) to select cells whose value is the empty string.
Because nothing is evaluated, list shows every (cell, task) combination;
when filtering and the resolved command line appear only at run time.
Common options
These options are accepted by both run and list (place them after the
subcommand, e.g. uv-matrix run -v --no-color):
--config PATHRead this
pyproject.tomlinstead of discovering one.--project DIRUse
DIRas the project root (andDIR/pyproject.tomlas the config, unless--configis also given). uv-matrix changes to this directory before running.
-v,--verboseIncrease verbosity (repeatable). Each level adds detail to a
run:-vadds theuv runcommand line to each job’s banner and names jobs skipped by awhencondition.-vvadds the job’s isolated environment directory, and lets uv print its own environment chatter — creating and removing virtual environments, resolving and installing dependencies — which is otherwise hidden by runninguvwith--quiet. Each further-vis forwarded touvas its own-v.
A job’s command output (e.g. test results) is always shown, regardless of the level.
-q,--quietDecrease verbosity (repeatable). At
-q,runprints only failures.--no-colorDisable colored output. Color is also disabled when the
NO_COLORenvironment variable is set, or when output is not a terminal.
uv-matrix --version prints the version and exits.
Shell features in run
A task’s run is executed through a shell, so it can use shell features —
pipes, &&, redirects, $VAR expansion — and they all run in the job’s
environment.
Environments
Each job runs in its own isolated environment under .uv-matrix/<key>/
(analogous to tox’s .tox/<envname>/), selected via the
UV_PROJECT_ENVIRONMENT variable. This keeps your project’s .venv untouched
and stops one job’s groups/extras from leaking into another. The directory
name is keyed by everything that determines the environment (Python version,
groups, extras, uv-args), so identical environments are reused across runs
and different ones never collide. Add .uv-matrix/ to your .gitignore.
A task’s own env value for UV_PROJECT_ENVIRONMENT overrides the default.