Skip to main content
reticle verify is the CI command: no model in the loop, no MCP. On a free port it boots the engine in drive mode, waits for the in-page SDK to dial back, replays every saved flow, renders the verdict, and exits. When a Reticle daemon already owns the port, it asks that daemon to replay the flows instead.
It uses the same runner and the same verdict as the MCP and HTTP paths, so a platform agent that can only run a shell command gets a byte-identical artifact.

Arguments and flags

On Windows PowerShell, write the predicate to a file and pass --expect-file so the JSON never goes through shell quoting:

Compare two builds

--against answers one question: did the change alter what users see? It drives both running builds with the same actions and reports each difference in what a person can read and use.
  • The actions. Each saved flow in .reticle/flows/ (with --select, only the flows with those labels) is replayed one step at a time, and the page is read after every step. Then the explorer of reticle_verify { action: "crawl", exhaustive: true } walks the reference breadth first. It fills each empty text field with its own value (text 1, text 2), clicks each control, and waits a short time after each action. Both builds then get the same list of actions.
  • What is compared. The accessibility tree, reduced to what a person sees: names, text, values, and the checked, selected, disabled, expanded and pressed states, in page order. Case and spacing are folded. Refs, focus, layout, test ids, source locations and component names are removed.
  • Noise. The reference is driven two times, each time in a new browser. A line that differs between those two runs (a clock, a random id) is ignored. A step that the reference did on only one of its runs is not compared.
  • A step the candidate cannot do. When the reference did a step and the candidate could not (the control is gone, or a flow anchor does not resolve), that is a difference.
The command uses the daemon on --port, and starts one when there is none. Each run is a leased browser tab, so the apps do not need reticle init. Each replayed flow step is written as a run, the same as any replay. The explorer clicks every control it finds. The action guard refuses a control it holds as destructive, and --confirm-dangerous removes that guard. Point --against at a build whose data you can change, not at a production database. Each difference prints the actions that first reached it, then - for the lines only the reference shows and + for the lines only the candidate shows. Differences with the same lines are printed one time, with the number of steps they were seen at. Exit 0 only when steps were compared and nothing differs. A run that compared no step exits 1, because it proved nothing. --results-json <file> writes { schemaVersion: 1, reference, candidate, identical, differences: [{ id, kind, actions, removed, added, seen }], journeys, compared, unstable, ignored, explored }. kind is changed or unreached. id is a hash of the difference’s lines, so the same difference has the same id on every run. ignored counts the lines skipped as noise, and unstable counts the steps not compared.

What it prints

The rendered run report on stdout. On failure it names the cause on stderr instead, and there are two refusals worth knowing:
Both of those are honesty guards, and both exit 1. Verifying nothing is never a pass.
A URL that does not answer is not one of those two. It surfaces as the underlying Playwright error, prefixed with verify failed:, and also exits 1. Real capture:
verify never starts a second daemon on a port that already has one. With a daemon there, it replays the saved flows (or answers --expect, or runs --explore) through that daemon. Only --headed and --storage-state need a browser of its own, so with a daemon on the port those refuse and print the ways out (drive through the running daemon, ask it via reticle_run, move both to another port with --port or RETICLE_PORT, or stop it). This bites most often on a developer machine, where an agent has usually left a daemon running; in CI the port is normally free.

Exit codes

Worked example

Recording the flows this replays

Flows are recorded interactively, then committed to .reticle/flows/.
Last modified on November 4, 2026