CI and the review bots#
Three GitHub Actions workflows and two automated reviewers stand between a
branch and a published site: CI gates a pull request, CI Full carries the
long jobs on main, and Docs Pages publishes this site.
CI — pull requests targeting main#
.github/workflows/ci.yml runs on pull requests whose base is main, on
pushes to main, weekly, and on demand. It is the gate; everything in it
should stay bounded in runtime.
The base-branch restriction has a consequence worth stating before the table:
a stacked pull request, which targets its parent branch rather than main,
gets none of these checks. See
Stacked pull requests.
Job |
What it does |
|---|---|
Typos |
|
Ruff |
|
Type checking |
|
Testing (Linux) |
the default pytest suite under a micromamba environment, installing |
Examples (Smoke) |
four examples under |
Documentation |
this site: |
PYOPENCL_CTX and PYOPENCL_TEST are pinned to portable:0 at the workflow
level, so CI always runs on PoCL rather than on whatever enumerates first.
The documentation job needs no device, but it does import volumential, so it
uses the same micromamba environment as the rest: pyopencl and loopy have
to be importable.
Two details of these jobs are load-bearing rather than incidental, and both were added in #151:
The smoke job runs every one of its commands in one
run:block, and the shellsetup-micromambagenerates does not pass-e. Without theset -euo pipefailthat now opens the block, only the last command’s exit status reaches GitHub, and a failing example is reported green — which is exactly what happened tohelmholtz2d.pyfor as long as it was broken. Keep thesetline if the block is ever rewritten, or give each command its own step.Testing (Linux)installs thefmmlibextra, which resolvespyfmmlibto a Git source rather than to a release and so builds it from source against the Fortran compiler.test-conda-env-py3.ymlinstalls. That build is what givestest/test_fmmlib_batched_stages.pyits batched{l,h}{2,3}dformmp_imanyentry points; against a released wheel, PyPI’s or conda-forge’s, eight of its tests skip and the batched-P2M path has no CI coverage (see Installation).Two details of that are worth knowing before editing the step. First,
uv pip installdoes not readuv.lock— onlyuv syncdoes, and an exact sync would uninstall the conda-provided half of the environment — so the[tool.uv.sources]entry, which names a repository but no revision, would float on upstreammain..github/scripts/pyfmmlib_requirement.pyreads the locked commit and prints it as a requirement, which the step passes touv;uv.locktherefore stays both the one place the revision is written down and the thing that decides what CI builds. Second, the step then imports the four wrappers, becauseFPNDFMMLibExpansionWranglerfalls back to the serial per-box path without complaining when they are missing: a build that silently fell back has to fail the environment rather than quietly reduce the suite to what it covered before.
The link check runs last on purpose: it is the only step whose outcome depends
on hosts nobody here controls, so a rate-limited or unreachable third party
cannot stop the deterministic checks from being reported. The URLs that cannot
be checked from a runner at all — a publisher and an OpenCL specification page
that answer a runner with 403, and the local sphinx-autobuild address — are
named one by one in linkcheck_ignore in conf.py, each with its reason. They
are spelled out as single URLs rather than as hosts, so that the next link to
the same site is still checked.
To review a change to the site rather than to its build, download the
docs-html-* artifact from the run’s Artifacts section and open
index.html. It is built with the command the deployment uses, so it is what
would go live; it is kept for 14 days.
Note what the smoke job does not cover: laplace3d.py, poisson3d.py
and branched_flow_helmholtz2d.py run only in the Examples job of
CI Full, which has no pull_request trigger. A change that breaks one of
those is not caught before it reaches main.
CI Full — main, weekly, and on demand#
.github/workflows/ci-full.yml does not run on pull requests.
The documentation build used to live here. It moved into CI in 2026-09, so
that a broken page, a dead link or a docstring that fell off the API reference
fails on the pull request that caused it rather than on main; CI also runs
on pushes to main and on the weekly schedule, so nothing was given up by the
move.
Job |
What it does |
|---|---|
Testing (macOS) |
the suite on macOS |
Full Accuracy Tests |
scheduled or manual only — collects and runs the |
Examples |
the maintained examples at full settings, with a cached Laplace 3D table |
The full-accuracy job runs on a GitHub-hosted CPU runner, on the PoCL CPU that
PYOPENCL_CTX=portable:0 selects: the marked tests build their context from
that variable (see Tests and markers). Until 2026-09 they looked for an fp64 GPU
instead, found none on the runner, and all of them skipped while the job
stayed green. So the job now names the device first and stops if it is not an
fp64 PoCL CPU, and after the run it fails if any test was skipped. The
collection step still runs on its own, so marker drift and import errors
surface before the long run starts. Both steps select the marker over the
whole test directory. Until 2026-09 they named two files instead, and the
five full_accuracy cases of test_windowed_rke.py ran in no CI job.
At full size the tier took 90 minutes on the runner, 57 of them in the two 3D
split-versus-nonsplit tests. So the job sets
VOLUMENTIAL_FULL_ACCURACY_REDUCED=1, which runs those two at a reduced size
(see Tests and markers). In two runs at that size the tier took 55 and 38
minutes; the runners had different CPUs. The full size is for a manual run.
Adding a documentation dependency#
Every new documentation dependency goes into the doc extra of
pyproject.toml. Both jobs that build this site — Documentation in CI and
Build in Docs Pages — install .[test,doc], so anything in the base
dependencies, the test extra or the doc extra is installed for them; only
an extra that neither selects (fmmlib and gmsh_support, for instance) is
missing.
The doc extra is where a documentation dependency belongs regardless, because
it is what uv sync --extra test --extra doc gives a contributor locally.
A dependency left out of the extra fails the Documentation job of the pull
request that introduced it, which is the point of the job running there.
Docs Pages — publishing the site#
.github/workflows/docs-pages.yml builds this site from main and deploys it
to GitHub Pages. It runs on every push to main, and manually — a
workflow_dispatch exists for the first publication, when there may be no new
commit to trigger one. Both jobs are guarded by
if: github.ref == 'refs/heads/main', because a dispatch takes a ref and an
unguarded one would publish a feature branch to the production site.
It has two jobs: Build, which runs the same
sphinx-build -W --keep-going -n -b html as CI and hands the output to
actions/upload-pages-artifact, and Deploy, which calls
actions/deploy-pages. The workflow is read-only by default and the two
grants a deployment needs — pages: write, and id-token: write for the OIDC
token the deploy action exchanges for one — sit on Deploy alone, so the job
that runs the build cannot reach the Pages API. It serialises on a single
pages concurrency group with
cancel-in-progress: false, so two pushes queue rather than race and a deploy
in flight is never cancelled half-way.
The link check and the coverage reports are deliberately not repeated here.
They gate a change in CI; a third-party host going down afterwards should not
block the deployment of pages that already passed them.
Pages has been enabled for the repository since 2026-09-13 (Settings → Pages
→ Build and deployment → Source: GitHub Actions, a setting the maintainer
owns), and the site is https://xywei.github.io/volumential/. The workflow
does not enable Pages for itself — actions/configure-pages can, with
enablement: true and a token beyond GITHUB_TOKEN, and turning on a public
site is not a decision a workflow should make. If the setting is ever turned
off again, Build still succeeds and Deploy fails with Get Pages site
failed; nothing else is affected.
With the site live, the version switcher’s json_url in doc/source/conf.py
is the absolute https://xywei.github.io/volumential/_static/switcher.json
(so a future tagged build reads the current index rather than its own frozen
copy), README.md and the repository homepage point at the site, and
html_baseurl names the same URL for the canonical links, sitemap.xml and
the OpenGraph metadata.
The review bots#
Pull requests are reviewed by the maintainer and by two automated services:
OpenAI Codex reviews when a pull request is marked ready for review. Comment
@codex reviewon the pull request to request another pass after pushing fixes.CodeRabbit reviews on the same trigger and comments inline.
Treat their findings as review comments, not as a gate: fix the correct ones, reply on the thread saying what changed, and resolve it. The maintainer merges; the bots do not.
Because the bots review on ready for review, a draft pull request gets no automated pass. Open it ready when you want one.
Stacked pull requests#
A branch stacked on another open pull request targets that branch, not main,
and says so in its body. Merge the stack in order, and do not delete a base
branch while a child is still open — deleting a base closes its stacked
children.
Two things do not work on a stacked pull request, and both are worth planning around:
CIdoes not run. Itspull_requesttrigger is restricted to basemain, so a stacked pull request has no lint, type, test or example job. Run them locally, and expect the first check to happen when the stack reachesmain.CodeRabbit does not auto-review, because auto-reviews are limited to the default branch. Ask for one with
@coderabbitai review. Codex reviews stacked pull requests normally.