# pytest-xdist workers collected different tests

Classification: flaky
Frameworks: pytest, CI
Canonical: https://automationtester.in/failures/pytest-xdist-different-tests-collected

## Recognizable error

```text
Different tests were collected between gw0 and gw1. The difference is:
```

## What it means

Every pytest-xdist worker collects the suite on its own, and the controller needs all of them to produce the same test IDs in the same order. When they disagree, no test runs. It often fails on some runs and passes on others, which makes it look flaky. The first worker in the message is the one whose collection reached the controller first, usually gw0, and every other worker is compared against it.

## Minimal repro

Run on 2026-10-23 with pytest 9.1.1, pytest-xdist 3.8.0, Python 3.11.14, macOS, arm64.

test_users.py:

```python
import pytest

ROLES = {"admin", "editor", "viewer", "billing", "support"}


@pytest.mark.parametrize("role", ROLES)
def test_role_can_log_in(role):
    assert role
```

command:

```shell
python -m pytest -q -n 4
```

output:

```text
_____________________________ ERROR collecting gw1 _____________________________
Different tests were collected between gw0 and gw1. The difference is:
--- gw0

+++ gw1

@@ -1,5 +1,5 @@

 test_users.py::test_role_can_log_in[admin]
-test_users.py::test_role_can_log_in[support]
+test_users.py::test_role_can_log_in[billing]
 test_users.py::test_role_can_log_in[viewer]
 test_users.py::test_role_can_log_in[editor]
-test_users.py::test_role_can_log_in[billing]
+test_users.py::test_role_can_log_in[support]
To see why this happens see 'Known limitations' in documentation for pytest-xdist

# The same block follows for gw0 against gw2 and gw0 against gw3, then:
3 errors in 0.38s
```

## Ranked causes, checks, and fixes

### 1. parametrize iterates a set

String hashes are salted with a random value per Python process, so a set of strings iterates in a different order in each worker. The diff shows the same IDs, reordered. All 34 unsorted runs of the repro on four workers failed at collection.

Diagnostic: If every line in the diff appears once with + and once with -, only the order differs. Look for set, frozenset or set operations feeding parametrize or a fixture's params.

Fix: Pass `sorted(...)` instead of the set. The pytest-xdist known-limitations page gives the same fix.

### 2. Parameters are generated at import time

uuid4(), random values without a fixed seed, or timestamps in parameters or IDs give every worker different test IDs, not just a different order. Sorting does not help: a sorted list of three uuid fragments still differed between gw0 and gw1.

Diagnostic: If the + and - lines carry different IDs, a value is generated per process.

Fix: Generate the data from a fixed seed, or give the cases explicit ids. With ids=["first", "second", "third"] the uuid repro collected cleanly, but each worker still saw different data, so a fixed seed is the better fix.

### 3. Collection reads state that changes while workers start

Parametrizing from a database query, an API call, or the files in a directory that a session fixture or another worker writes to means each worker can read a different answer at collection time.

Diagnostic: Find parametrize arguments or pytest_generate_tests hooks that do I/O, and check whether anything writes to that source during the run.

Fix: Build the case list before the run (a checked-in file or a fixed snapshot), or move the lookup into the test body.

## Verified fix

Give every worker the same ordered list.

test_users.py:

```python
@pytest.mark.parametrize("role", sorted(ROLES))
def test_role_can_log_in(role):
    assert role
```

Evidence: Re-run on 2026-10-23 with pytest 9.1.1 and pytest-xdist 3.8.0 on Python 3.11.14: after switching to sorted(ROLES), five runs with -n 4 each printed 5 passed. Putting the bare set back made the next five runs end in 3 errors each. In the 34 earlier runs whose messages we read, the base worker was gw0 every time, never gw1.

## When this is not the cause

- **A test failure you can retry.** No test ran. Rerunning the job only changes whether the hash order happens to agree, which is why it can look intermittent with fewer workers or values.
- **Fixing it with PYTHONHASHSEED.** PYTHONHASHSEED=0 did make the set repro pass five times out of five, because every worker then hashes the same way. It hides the set rather than removing it, and it does nothing for the uuid case.
- **A broken pytest-xdist install.** The error comes from the controller comparing worker collections, which is exactly what xdist is for. The same suite without -n cannot show it.

## Prevention

- Never feed a set or dict built from one into parametrize.
- Seed any generated test data with a constant.
- Run the suite with -n 2 in CI even when it is small, so collection drift fails early.

## Primary references

- [pytest-xdist: known limitations, order and amount of tests](https://pytest-xdist.readthedocs.io/en/stable/known-limitations.html)
- [pytest-xdist source: report.py, the message](https://github.com/pytest-dev/pytest-xdist/blob/master/src/xdist/report.py)
- [Python data model: __hash__ and hash randomization](https://docs.python.org/3/reference/datamodel.html#object.__hash__)

Triage this failure locally: https://automationtester.in/tools/test-failure-triage