CI workflows + merge queue ========================== The repo runs on GitHub Free, which means no native merge queue. A custom **label-driven auto-update + auto-merge workflow** stands in: PRs that opt in via the ``automerge`` label get rebased onto ``main`` whenever ``main`` moves and merged the moment CI is green. This page is the contract for that workflow plus a tour of the five permanent CI workflows under ``.github/workflows/``. .. contents:: Page contents :local: :depth: 2 Workflows at a glance --------------------- .. list-table:: :header-rows: 1 :widths: 24 16 60 * - Workflow - Triggers on - What it does * - ``Tests`` - PR + push to main - Lint, unit tests, optional-solver-backends matrix. Three jobs: ``Lint``, ``Unit tests (py3.10)``, ``Optional solver backends``. pytest-xdist for parallelism. * - ``docs`` - PR + push to main - Sphinx build (strict mode — warnings as errors). Includes the ``Verification manual tests`` job (split out in #538) and the docs-CI gates landed in #583 (numpydoc per-symbol, doctest, linkcheck). * - ``Verification manual tests`` - PR + push to main - Runs ``tests/cross_solver/test_verification_round_trip.py`` as its own status check so the harness's signal is independent from the rest of the suite. * - ``Auto-update PRs behind main`` - ``push: main``, ``workflow_run`` from Tests / docs, ``workflow_dispatch`` - Walks every PR carrying ``automerge``, rebases each onto the new ``main``, and merges any that are green-and-current. * - ``Track main / release CI failures`` - ``workflow_run`` from Tests / docs / Auto-update - Opens / updates a tracker issue when a failure on ``main`` (or in the merge queue) needs follow-up. * - ``Benchmarks (nightly trend)`` - schedule: nightly - Runs the perf harness, posts trend updates. See :doc:`performance`. * - ``Release readiness`` - push to ``main`` - Diffs perf numbers against the previous tagged release; regressions block docs deploy. * - ``docs-linkcheck`` - schedule: nightly - Runs Sphinx ``linkcheck`` on the published docs; external-link rot opens a tracker issue. Each workflow lives at ``.github/workflows/.yml``. The label-driven merge queue ---------------------------- GitHub Free doesn't ship a merge queue, so the repo synthesises one with a custom workflow. The ``automerge`` label is the opt-in. Lifecycle: 1. Author opens a PR. CI runs. 2. Author adds the ``automerge`` label when the PR is ready to ship — code review passed, comments addressed, no pending changes. No label = the PR sits. 3. The ``Auto-update PRs behind main`` workflow watches: * ``push: main`` — main moved, walk every labelled PR and rebase any that are behind. * ``workflow_run: [Tests, docs] completed`` — CI just finished on a labelled PR; if green and current with main, merge it. * ``workflow_dispatch`` — manual kick to drain the queue. 4. The workflow performs two passes per fire: * **Rebase pass** — parallelise over labelled PRs that are behind main; for each, do a server-side rebase via ``gh pr update-branch``. * **Merge pass** — serial over labelled PRs that are now CLEAN; merge with squash, refresh between merges so successive PRs each see the latest main. 5. If a rebase produces a merge conflict (``DIRTY`` state), the workflow stops on that PR and posts a comment. The PR author resolves manually (per :doc:`fixtures_and_decks` if it's a fixture conflict; via merge-commit if force-push isn't allowed on the branch). The ``automerge`` label is **opt-in** for two reasons: * PR authors sometimes want a stale-PR window after CI passes (e.g. waiting on a downstream review). Without the label the workflow ignores the PR. * Stale CI on a no-longer-current PR shouldn't get rerun on every ``push: main`` — that saturates runners. Concurrency ~~~~~~~~~~~ The workflow uses ``concurrency: group: auto-update-prs, cancel-in-progress: false``. ``false`` matters: cancelling an in-flight merge pass can leave the queue in a partial state (some PRs merged, some not, with inconsistent ``main`` references). Better to let each fire finish. The known limitation: the GitHub-issued ``GITHUB_TOKEN`` cannot trigger downstream ``workflow_run`` events. When the workflow merges a PR (which produces a ``push: main``), Tests/docs run on that push but the auto-update-prs workflow does **not** re-fire on ``workflow_run`` from those runs. The workflow handles this by draining the queue serially within a single fire — never deferring to "the next workflow_run will pick it up." The active-PR cap ----------------- A standing rule per ``~/.claude/skills/pr-workflow``: > **Active-PR cap: at most 2 active labelled PRs per agent at a > time.** The cap exists because: * Every ``automerge`` PR is rebased on every ``push: main``. N PRs × M main-pushes-per-day = N × M CI runs. With N > 2 the runner saturation is visible in queue times. * The merge pass serialises across PRs. More than 2 outstanding means the second one always blocks behind the first. When the cap is exceeded, hold off opening more PRs until at least one drains. The "no stale green PRs" rule in the ``pr-workflow`` skill enforces the other side: every CLEAN labelled PR you own must be merged immediately, never queued indefinitely. Pre-commit ---------- ``pre-commit`` runs locally on every commit. Hooks: * ``ruff`` — lint + format. No CI lint failures should be surprises. * ``trim trailing whitespace``. * ``fix end of files``. * ``check yaml``, ``check toml``, ``check for added large files``, ``check for merge conflicts``. * ``don't commit to branch`` — refuses commits to ``main`` locally. When you commit and a hook reformats a file, the commit *didn't happen*. Re-stage and re-commit. Failure tracking ---------------- The ``Track main / release CI failures`` workflow watches Tests / docs / Auto-update outcomes. When a failure surfaces on the ``main`` branch (or in the merge queue), it opens or updates a tracker issue tagged ``ci-failure-merge-queue``. The PR author who triggered the failure (or the agent on point) drives the fix. Common pitfalls --------------- * **Merging without the** ``automerge`` **label.** The workflow ignores unlabelled PRs. If you bypass auto-merge and merge directly, the queue's other PRs miss a chance to rebase against your push for the few seconds between the push and the next ``push: main`` workflow_run firing. * **Force-pushing to a feature branch with the label.** Most permission policies in this repo deny force-push. When a rebase needs an in-place rewrite, prefer a ``git merge origin/main`` resolution (resulting merge commit gets squashed at merge time). * **Setting** ``cancel-in-progress: true`` **on the queue workflow.** Don't. An in-flight merge pass cancelling mid-rebase / mid-merge leaves the queue in a partial state. * **Pushing N > 2 labelled PRs as one agent.** Watch the active count; the cap is per agent, not global. * **Skipping pre-commit hooks** (``--no-verify``). Don't — the hooks are the same lint configuration CI uses; bypassing them locally just means CI fails 5 minutes later. * **Not flagging a regression in** ``PERFORMANCE.md`` **when release-readiness fires.** Every regression has a written diagnosis (see :doc:`performance`). Where things live ----------------- .. list-table:: :header-rows: 1 :widths: 32 68 * - Concern - Path * - GitHub workflows - ``.github/workflows/.yml`` * - Pre-commit config - ``.pre-commit-config.yaml`` (repo root) * - PR-workflow rules (the ``automerge`` label, active-PR cap, no-stale-greens) - ``~/.claude/skills/pr-workflow/SKILL.md`` * - Failure-tracker workflow - ``.github/workflows/ci-failure-tracker.yml`` * - Auto-update workflow source - ``.github/workflows/auto-update-prs.yml``