Skip to main content
One question this file answers: I changed some files. Which command do I run? The why behind the gate design (tiers, the merge-gate/release-gate split, what is still unbuilt) lives in gate-plan.md. This file is the routing table.

0. Last verified

Every gate below was executed end to end against main on 2026-08-12 (macOS, M-series, v2.6.0). A green row means somebody watched it go green, not that it is supposed to be green. pnpm bench was broken and nobody knew. suite-rre.mjs recorded four flows that asserted no observable consequence and then demanded a pass verdict from reticle_flow_verify — which correctly grades an assertion-free suite unverifiable. The product got more honest about false greens; the benchmark measuring it did not follow, so the whole run aborted at script 9 of 10 and replay-determinism never ran at all. Each flow now carries a success oracle. This is the failure mode bench/ is most exposed to: nothing in CI runs it, so it can only rot silently. pnpm gate:install (~15 min) and the Windows / Rust jobs were not run in this sweep — they are CI-only or network-bound. They are green on main per the last CI run, which is a weaker claim than every row above, and is stated that way on purpose.

1. The routing table

Find the row that matches what you changed. Run its commands. That is the whole rule. Why routing exists. The full set is roughly 35 minutes. A gate people resent is a gate people route around, so only the tier that can see your change is worth your time. CI runs everything regardless — routing costs you a slower red, never a missed one.

2. Every gate, and what each one can actually see

Each gate exists because the ones above it are blind to something. That blindness is the column that matters. The single required status check is gate. It passes when every job above either succeeded or was deliberately skipped by path routing, and fails on anything else. Adding a job to ci.yml is half the work; adding it to gate’s needs: list is the other half — a job missing from that list runs, reports, and is structurally incapable of blocking a merge.

Guards that self-test

Four checks prove they can still fail before they are trusted. A guard that has never refused anything is not a guard, so each has a negative control that CI runs first:

3. Gates that are not automatic

These are real and they work; they are not on the PR path, so they only run when somebody runs them.

4. bench/ is not a gate

bench/ is measurement and research, not a merge check. Nothing there blocks a PR, nothing runs in CI, and it is allowed to bit-rot in a way a gate is not. Read bench/README.md before touching it — it says which scripts are live and which are one-off studies kept as evidence for a published claim. The one exception worth knowing: pnpm bench + pnpm bench:gate is a working regression gate for the replay numbers, and it is run by hand before a release.

5. When a gate fails and you think it is the gate’s fault

Sometimes it is. The specific failures worth recognising:
  • EADDRINUSE / “died during boot” — a previous run left something on :8787, :4310, or :3100. run-ci.sh frees these on exit; if it was killed, free them by hand.
  • Killing port 4400 with lsof -ti tcp:4400 | xargs kill -9 — this SIGKILLs the reticle mcp proxy too, because the proxy holds a client socket on the bridge port. Always add -sTCP:LISTEN. This is the root cause of most “the MCP went down” reports.
  • A timing assertion — if a test asserts Date.now() - t < N, that is a bug in the test, not a flake to re-run. Assert the bound (output size, a truncation flag), or use a generous per-test timeout. See harness-rules.md.
  • An INCONCLUSIVE verdict — the harness is telling you the transport did not stay up, so it is claiming nothing about the product. That is the harness working, not the product failing.
The four rules every tier obeys, and the incident behind each, are in apps/e2e/harness-rules.md.
Last modified on August 14, 2026