The CI Matrix#

Serena’s CI has a problem most projects never meet: sixty-odd languages, each with its own toolchain, and no runner that could hold them all at once. The answer is a matrix with opinions — and the opinions are written down, in the header of .github/workflows/pytest.yml. This page walks through them; when CI surprises you, that header is the primary source.

Five batches, three operating systems#

Tests run as parallel matrix jobs over (OS × batch). The five batches are marker-based — jvm, native, other-langs, niche, and a catch-all for everything unmarked — and the batching is deliberately by pytest marker, not pytest-xdist: tests share fixtures, language-server processes and on-disk resources, so in-process parallelism would race them.

Two properties keep the partition honest: each batch installs only the toolchains its languages need, and catch-all is defined as the negation of all the named groups — every test lands in exactly one batch, and adding a language cannot desync the split.

Where the matrix thins out#

The default is the full cross-product, with two kinds of exception:

  • niche runs on ubuntu only. Its toolchains (lean4, ocaml, nix, julia, R, perl) are heavy and slow — not worth the Windows and macOS cost.

  • An OS-specific language inside an all-OS batch (swift on macOS, haskell on Linux) is skipped one level down, by the central guard in test/conftest.py — never by the matrix.

That split matters when you add a language: OS gating belongs in the conftest, not in the workflow file.

The ceilings#

GitHub’s free tier caps runs at 20 concurrent jobs and 5 concurrent macOS jobs. The current matrix is 13 jobs, 4 of them macOS — inside both limits, and anything that adds batches or operating systems has to stay inside them too, or jobs queue. Superseded runs on the same ref are cancelled outright to free capacity, and every job carries a 60-minute timeout: a healthy warm-cache run peaks around 16 minutes, and the ceiling exists because a hung job once burned the full six hours.

Caching#

Language servers are cached per (OS × batch); the uv venv is keyed on uv.lock; several individual toolchains and the web fixtures’ npm state have caches of their own. Most of the niche toolchains restore from cache, which is why that batch stays fast despite its size.

What this means for your pull request#

  • poe lint and poe type-check run once per OS, in the catch-all batch — the format gate is the first thing to fail, in the first minute.

  • Your language’s tests run in whichever batch its marker belongs to; nothing to configure.

  • A red run is usually one red job. Read that job’s log before concluding anything about the other twelve.

  • The docs build (Sphinx, warnings as errors) and a spell check run alongside the tests.