---
title: reeval run
description: Plan and run evals, with every flag, filter rule and output option.
---
> ## Documentation Index
> Fetch the complete documentation index at: https://docs.reeval.ai/llms.txt
> Use this file to discover all available pages before exploring further.

`reeval run` plans the evals that match `include` and your filters, prints the plan, runs every attempt and prints a result block per eval.

```bash
npx reeval run hello --runs 2 --ui plain
```

## Help

[//]: # (generated:cli-help:run)

```text
reeval run [filter...]        Run evals matching config.include; filters are path, name or cell substrings
                              (parts of one kind OR; a name and a cell key are one kind)
  --profile <name>            Size the run with a profile from reeval.config.ts (also REEVAL_PROFILE;
                              default: defaultProfile.ci in CI, else defaultProfile.local)
  --dry-run                   Print the plan (evals, cells, attempts, time and cost) and exit 0;
                              with --json, as one JSON document (every cell, and what's excluded)
  --json                      Print the run's results (suiteResult from reeval/view) as one JSON
                              document on stdout when it ends; progress goes to stderr
  --force                     Run even when the plan is over budget.attempts
  --runs <n>                  Attempts per cell, overriding the config, groups and evals
  --concurrency <n>           Attempts running at once across evals and cells (default 8, at
                              most one per CPU and GB of Docker memory; also REEVAL_CONCURRENCY)
  --tag <tag>                 Only evals with this tag; !tag excludes it; repeat to combine (AND)
  --record                    Also record the run into the history folder (see config.record)
  --ui <mode>                 live, plain or agent (default: agent under a coding agent, plain
                              in CI or when piped, else live; live prints plain for now)
  --log-order <order>         stream (every line as it happens) or grouped (each attempt as one
                              block when it ends); default grouped in CI, else stream
  --log-prefix <prefix>       task or none: the [eval#n harness] prefix on detail lines
                              (default task, none inside GitHub Actions groups)
  --output-logs <level>       full, errors-only or none: whose detail lines print (default
                              errors-only, failing attempts only; full with -v)
  -v, --verbose               Print each attempt's phase trace as it happens: plan, image,
                              container, environment, brokering, turns and tool calls, checks,
                              judge calls, gateway requests and teardown
  -vv, --trace                Also raw payloads, docker commands, headers and DNS decisions, and
                              keep every attempt's trace.ndjson (else only failing attempts')
                              Either way each attempt's debug.log is in .reeval/debug/<run>/ (local only)
  --resume [ref]              Continue a stopped, interrupted or budget-stopped run: same run id,
                              skip completed attempts, restart in-flight ones. Without a ref, the
                              newest resumable (Stopped staging, or an interrupted/budget scratch),
                              not assembled latest. Ref is a run id or unique prefix
  --no-insights               Skip insights for this run: no analysis.model calls, the blocks
                              print their facts only (also analysis.insights: false)
```

[//]: # (/generated:cli-help:run)

## Filters

```bash
npx reeval run evals/hello.eval.ts      # a file
npx reeval run "greet module"           # evals by group or name
npx reeval run "greet module › creates" # an eval in a group, by its id
npx reeval run with-house-greeting      # cells by key
npx reeval run skill antigravity        # skill.eval.ts's Antigravity cells
```

- A filter part that names an eval file picks files. Every other part picks evals by name or cell key, as a case-sensitive substring of the eval id or the cell key (`cursor:composer-2.5/with-house-greeting/docker`).
- Parts of one kind add up (OR). The two kinds narrow each other, and the plan's rows and totals count only the cells that are left.
- An eval name and a cell key, harness included, are the same kind. `reeval run claude-code:claude-sonnet-5 "greets by name"` plans every Claude Code Sonnet cell plus `greets by name` on every harness. To run one eval on one harness, filter by the eval and set a profile's [`limit.harness`](/reference/profiles#limit).
- A part that matches nothing exits 2 and names it.
- `--tag slow` keeps evals tagged `slow`, `--tag '!slow'` drops them. Repeated `--tag`s must all hold. A profile's `tags` work the same way.

## The plan line

Every run starts with it, before anything runs:

```text
plan  run 20260101T000000Z_nogit_ada-lovelace_k3f9 · 1 eval · 2 cells · 4 attempts · ~50s serial (~13s at concurrency 4)
```

The estimate is the median of each eval's recent attempts on that harness (the last 30 runs, recorded and scratch), else the harness's median, else 3 minutes an attempt. A `fakeHarness` attempt with no history is 1 second in `fakeSandbox()` and 30 seconds in `docker()` or `podman()`, the sandbox's setup. With a profile and a budget it also says which profile, why, and whether the plan fits:

```text
plan  run 20260926T174055Z_b010670_ci_vdfy · profile pr (auto: CI is set) · 18 evals · 19 cells · 19 attempts · ~57m 00s serial (~14m 15s at concurrency 4) · budget 20 attempts ✓
```

`--dry-run` adds a row per eval, then, when Docker is running, a line per sandbox image saying whether it's ready, to build, stale or missing (a `from` tag is looked up in its registry, never pulled), and stops with exit 0. Neither needs credentials, and Docker is optional.

`--dry-run --json` prints the whole plan, every cell and what was excluded, as one JSON document, with each image step's `status` and `detail`.

```text
images  4 sandbox images: 1 ready, 3 to build
  ✓ docker · cursor                     ready
  + docker+0ab0e263 · cursor + install  to build: install 1 command, not built yet
  + python · cursor                     to build: from python:3.12-slim, on python:3.12-slim (f77ac9e44ae9)
  …
```

## The cost estimate

```text
cost  estimated $1.20–9.00 at prices.json over models.dev list prices, judge included · no history, rough
cost  $0.77–6.12 priced (claude-sonnet-5, gpt-5.6-luna, judge) at models.dev list prices; composer-2.5: 1–13M tokens, no list price · no history, rough
cost  estimated 5–38M tokens, judge included · no prices: models.dev unreachable, next try in 60m · no history, rough
```

`--dry-run` ends its plan with what the run should cost: tokens, priced in USD where it has a price for the model. It's an estimate for deciding whether to run, not your bill: subscriptions and included usage, negotiated prices and re-runs aren't in it.

| Part | Where its tokens come from |
|---|---|
| Agent | The same eval and cell's counted attempts in the last 30 runs (recorded and scratch), else the same eval on that harness: their middle half, never narrower than −20% / +25% of the median. With neither, a typical coding attempt: 300k–2M prompt tokens, mostly cached, and 3–20k out |
| Judge | As many calls an attempt as the eval's past attempts made, on their model, else 0–1 calls on the run's `judge.model`; 5–60k tokens in and 0.5–2k out a call |

When some models have no price, the line doesn't add up to a total: it says what the dollars cover (`priced (judge only)`, `priced (claude-sonnet-5, judge)`) and lists each unpriced model's tokens. Reasoning tokens are priced as the output they're part of.

<Warning>
Without history, judge calls are priced on the run's `judge.model`, even for an eval or group that sets its own. Once the eval has run, its past judge calls set the model.
</Warning>

reeval keeps no prices of its own, and never writes one to a record. The dry run looks them up as it prints:

- **[models.dev](https://models.dev)**, by default: the Anthropic, OpenAI, Google and xAI list prices, and Vercel AI Gateway's `provider/model` ids, cached for a day in reeval's host cache directory. The request sends nothing about your project, gives up after 2 seconds or 32 MB, and goes through `HTTPS_PROXY` when `NODE_USE_ENV_PROXY=1` is set (Node 22.21 or later), as the judge's calls do; a failure isn't retried for an hour. Offline, or when models.dev answers without prices, it uses an older copy and says so (`models.dev list prices of 2026-10-20 (offline)`, `(models.dev answered HTTP 500)`); with none, the line is tokens only.
- **Your own**, with `REEVAL_PRICES=prices.json`: USD per million tokens by model id, in models.dev's field names (`input`, `output`, `cache_read`, `cache_write`), over models.dev's. A relative path is from the working directory. Any other field, ids that differ only in case, or a file that isn't such an object stops the dry run with exit 2. A model in the file takes its price from there, and every other from models.dev. Cursor's models aren't on models.dev, so this is how they get a price. When models.dev can't be reached, the file's prices are used alone. `REEVAL_PRICES=none` is tokens only, and fetches nothing.
- **Model ids** match as written (`anthropic/claude-sonnet-4.5`) or without the `provider/` prefix, case-insensitively and without a trailing effort (`gemini-3.8-flash-medium`), and Cursor's `params: { fast: 'true' }` looks up `composer-2.5-fast`. The fake agent and judge cost nothing, and questions and insights, cents, aren't counted.

```json title="prices.json"
{ "composer-2.5": { "input": 0.5, "cache_read": 0.2, "output": 2.5 } }
```

`--dry-run --json` has the detail under `cost`: `tokens` and `usd` as `{ low, high }` (`usd` null without prices), `agent` and `judge` each with `tokens` and `usd`, `unpricedTokens`, `attempts` by basis (`history`, `typical`, `free`), `pastRuns`, `pastAttempts`, `unpriced` (each model with no price and its `tokens`), `prices` (`modelsDev`, with `fetchedAt`, `stale` and, when stale, `why` the fetch failed, or only `why` there's no copy; and the `REEVAL_PRICES` `file`; or `why` there are no prices), and `cells`, each with its `model`, `judgeModel`, `basis`, `samples`, `attempts`, `agent` and `judge`.

## Results as JSON

```bash
npx reeval run --config reeval.fake.config.ts --json > results.json
```

```json
{
  "runId": "20261021T064035Z_02e3770_cursor-agent_nwqx",
  "status": "complete",
  "outcome": "pass",
  "errors": [],
  "tally": {
    "errors": 0, "pass": 16, "notGated": 0, "fail": 0,
    "infraError": 0, "cancelled": 0, "skipped": 0
  },
  "files": [
    …,
    {
      "path": "evals/skill.eval.ts",
      "evals": [
        {
          "path": "evals%2Fskill.eval.ts › house greeting › greets in the house style",
          "name": "greets in the house style",
          "across": "change",
          "family": false,
          "outcome": "pass",
          "cells": [
            {
              "key": "fake/plain/fake",
              "baseline": true,
              "gated": false,
              "passed": 0,
              "attempts": 1,
              "checks": [
                {
                  "label": "house greeting",
                  "outcome": {
                    "status": "ran",
                    "passed": 0,
                    "failed": 1,
                    "errored": 0,
                    "notRun": 0,
                    "assertion": "house greeting: expected command to succeed, …"
                  },
                  …
                }
              ],
              …
            },
            …
          ]
        }
      ]
    }
  ],
  "paths": ["evals%2Fgreetings.eval.ts › greets %25s in the house style", …]
}
```

`--json` prints the run's facts as one JSON document on stdout when the run ends; the progress and result blocks go to stderr. It's `suiteResult(record)` from `{{ pkg }}/view`, recomputed from the record, so `reeval show --json` still prints the record itself. The exit code is the same as without `--json`.

| Field | What |
|---|---|
| `outcome` | The run's outcome, which the exit code is read from: `skipped` when interrupted, else `fail` on any error outside an attempt |
| `errors` | Errors outside any attempt: an eval file's stray rejection or throw, a worker that died. Each fails the run |
| `tally` | Outcomes per block: each `evaluate.each` case and each other eval is one, and a family whose pooled `minLift` failed is one more fail. `notGated` counts the studies (every cell `gate: 'none'`), which `pass` leaves out, `cancelled` the blocks whose only failed tries the interrupt cancelled, and `errors` the run's `errors` |
| `files[].evals[]` | An eval, or a pooled `evaluate.each` (`family: true`) with its `cases`, `pooled` variants and `questions` summed over the cases |
| `across` | What the cells compare: `none`, `change` (variants), `harness` or `both` |
| `cells[]` | Each cell: counts (`infraError` and `skipped` apart from `failed`), `baseline`, `gated`, `fingerprint`, `context` (skill sources and files) |
| `checks[].outcome` | `ran` with its counts and first failure, or `not run` when no counted attempt reached it. A cell with no counted attempt lists every check the eval (or family) recorded as `not run` |
| `effect` | A variant against its baseline: `better`, `worse`, `same` or `unclear`. Fewer than 4 counted attempts a side is `unclear`, and so is a check the baseline never ran |
| `effort` | Median tool calls, agent time and tokens, from 4 counted attempts |
| `paths` | Every node's `path`, a stable id to cite it by: `<eval>/<case>/<harness>+<environment>+<sandbox>/checks/<label>` |

A sandbox that runs as root or with capabilities added back gets a line under the plan, one per config, profile, group or eval that set it. See [Privilege warnings](/reference/sandbox/docker#privilege-warnings).

## Resume a stopped run

`--resume` continues the same run: it keeps the run id, skips attempts that already
finished, and restarts ones that never wrote a record (or only wrote `aborted`).

```bash
npx reeval run --resume
npx reeval run --resume 20260101T000000Z_nogit_ada-lovelace_k3f9
npx reeval run --resume 20260101T0000
```

| Ref | Picks |
|---|---|
| None, or `latest` | The newest resumable run: a Stopped staging directory, or an `interrupted` or `budget` scratch record. Not `reeval show latest`, which after a crash is often the previous assembled run |
| A run id or unique prefix | That run. Git SHA prefixes aren't used: they'd pick the newest run at that commit, which is often complete |

A run is resumable when it isn't `complete` and no process for that id is still alive on this machine. The pid is what counts: a quiet heartbeat (a sleeping laptop) is still running, and resume won't destroy its containers. A second `--resume` of the same id is refused while the first CLI still holds it.

With no ref, a live leftover is named (`still running`) rather than reported as missing. `.reeval/current` points at the id from the moment the run starts, after the plan, so `--resume` can find a crash that never assembled. A usage error at plan time writes nothing.

In-flight attempts start again from the beginning, and completed ones keep their outcome, including a pass. The assembled record replaces the interrupted or budget scratch under the same id.

On GitHub Actions, the id goes to `GITHUB_OUTPUT` as `run-id` and to the step summary when the run starts, so the job log has it even if SIGKILL skips assembly. A re-run still needs the leftover staging (or interrupted scratch) restored onto the new VM.

## Before any attempt starts

| Check, in order | On failure, exit 2 without writing a run |
|---|---|
| A config is found, or `--config` names one | `No reeval.config.ts … here or above. Run reeval init.` |
| The config and every eval file load | The load error |
| `include` matches files, and filters and tags match evals | `No eval files in … match include: …` |
| `.only` isn't used in CI | |
| The plan fits `budget.attempts` | |
| The credentials of the harnesses and judge the plan uses are set, with no stray whitespace | The missing or malformed variables |
| Docker is running | |

Then it prepares every sandbox image the plan needs, three at a time, once each: see [Sandbox providers](/reference/sandbox/providers). One the config sets that can't be prepared stops the run there, exit 3 (2 for a missing `docker({ image })`); one a group or eval sets fails only the attempts that need it.

`reeval run` loads `.env.local`, then `.env`, from the config's directory, not the current one. Variables already set in your shell win, then `.env.local`, then `.env`, and the config and the evals both see them.

## Output flags

| Flag | Values | Default |
|---|---|---|
| `--ui` | `live`, `plain`, `agent` | `agent` under a coding agent, `plain` in CI or when piped, else `live` (the logs under one redrawn progress line) |
| `--log-order` | `stream`, `grouped` | `grouped` in CI, else `stream` |
| `--log-prefix` | `task`, `none` | `task`, `none` inside GitHub Actions groups |
| `--output-logs` | `full`, `errors-only`, `none` | `errors-only`: only failing attempts print their detail lines. `full` with `-v` or `-vv` |
| `-v`, `-vv` | | Off. See [Debug data](/reference/debug-data) |

Each has an environment variable: `REEVAL_UI`, `REEVAL_LOG_ORDER`, `REEVAL_LOG_PREFIX`, `REEVAL_OUTPUT_LOGS`. See [Output modes](/concepts/output).

## The result blocks

Every mode ends with a block per eval, in file-path order and then source order, then the tally and, while `analysis.model` is unset, a hint to set it (the judge's model never stands in for it). `summary.md` and `reeval show` print the same blocks. A block starts with the eval's verdict and name:

| Verdict | The eval |
|---|---|
| `pass` | Passed its gate, with no infra error |
| `fail` | Failed its gate, or a pooled family's `minLift` |
| `infra error` | Couldn't run: a sandbox, workspace or setup step failed |
| `cancelled` | Its only failed tries were cancelled by Ctrl-C or `SIGTERM`, so the agent didn't fail it |
| `skipped`, `todo` | `evaluate.skip` or `evaluate.todo` |
| `not gated` | Passed with every cell ungated (a study), so it doesn't read `pass` beside `0 of 2` |

Under the name, a pass prints nothing more. A fail prints each failed check with its first assertion (`failed` on one try, `N of M` on several), `not run` for a check that never ran, an error no check explains (`error`, `judge`, `timeout`, `cancelled`, `turn error`), and `+N more passed`. A compare prints a row per arm with `N of M` and its effect, then those checks a column per arm, then an effort table from four counted tries a side; [Output modes](/concepts/output#the-layout-follows-what-the-eval-varies) has each layout.

An error outside any attempt, such as an eval file's stray rejection, prints first as `error: …`; it fails the run (exit 1) even when every eval passed. An interrupted run still exits 130.

With `analysis.model` set, the blocks also carry the run's [insights](/reference/cli/analyze#during-a-run): written once the attempts finish and the exit code is fixed, within `analysis.maxTokens` and [fixed caps per eval and call](/reference/cli/analyze#the-caps). Ctrl-C while they run skips the rest and keeps the run's exit code. `--no-insights` prints the facts only.

The tally counts each eval, and each case of a family, once: `1 run error · 3 fail · 1 pass · 1 not gated · 1 infra error · 2 cancelled · 2 skipped`, run errors and fails first, zeros left out. A family whose pooled `minLift` gate fails counts one more fail. A study counts as `not gated`, never `pass`, whatever its attempts did; its arm rows have the real counts. Counts read `N of M`, with infra errors and cancelled and skipped tries named apart (`0 of 1 · 2 skipped`); `-v` adds each arm's difference and 95% interval. Judge scores per attempt, per-key judge rates and token usage per cell are in the studio and `reeval query`.

## Remarks

```text
1 fail · 1 pass
Set analysis.model in reeval.config.ts for answers to what each eval asks.

Remarks:
  coin: "lands heads" passed 2 of 6 tries over 2 runs with nothing changed, so one try can pass or fail it.
    fix  evals/coin.eval.ts:8  make "lands heads" check something every good try does, or raise runs so one try doesn't decide it
  coin: MCP server docs took 2s to start on each of 2 turns, in 3 of 3 tries.
    fix  evals/env.ts:9  install what docs needs in the image (install or dockerfile), so it doesn't fetch or build it at start-up
  …
```

After the tally and hint, a run lists what reeval noticed about the evals themselves, each with the source line to change. Only concerns print, at most three (`+N more (-v)`); `-v` adds the suggestions. Remarks never change the exit code.

reeval's own remarks come from fixed thresholds over the record and the history, so they print without `analysis.model`:

| Remark | Severity | When |
|---|---|---|
| `flaky-check` | concern | The cell is `flaky` in [`flaky_cells`](/reference/cli/query#views) (not from infrastructure), and the check both passed and failed in 2 of its newest 5 comparable runs, over 6 tries or more |
| `slow-mcp-start` | concern | A stdio MCP server took 2s or more to answer `initialize`, or never did. Turns count only where the server was started: every turn on a harness that starts servers per turn, the first on one that starts them per session |
| `proxy-only-egress` | concern | A failed attempt under egress `'proxy'` (the default) shows a connection error (`Network is unreachable`, `connection refused`, `could not resolve host`, …) in its error, failed checks or turns, and the gateway refused nothing: a raw TCP or UDP client, or one that ignores `HTTPS_PROXY`. Its fix is `egress: 'allow-all'` on the eval. See [Egress](/reference/egress#connections-that-never-reach-the-gateway) |
| `no-baseline` | concern | With `analysis.model`: the classification says the eval is about a change (a skill, a doc, an MCP server), and the eval has no `compare`. Never under a profile's `limit.compare: false` |
| `too-few-tries` | suggestion | A variant's and its baseline's pass rates differ by half or more, with fewer than 4 counted tries on a side |
| `check-never-fails` | suggestion | A check passed every try, in every cell, this run and in the last 5 recorded runs on the default branch with 20 tries or more ([`check_streaks`](/reference/cli/query#views)); on a variant, on both sides |

### Model remarks

```text
Remarks:
  answers kindly: The judge asks whether the answer is okay, which any reply passes.
    fix  evals/kinds.eval.ts:16  ask 'Does the reply thank the user by name and offer one next step?'
  adds a cache: The name says it adds a cache, but the prompt asks to remove the debug logging.
    fix  evals/kinds.eval.ts:4  rename it to 'removes the debug logging'
  …
```

With `analysis.model`, each eval's [insights](/reference/cli/analyze#during-a-run) call may also raise remarks, by the eval rubric (`skills/eval-rubric/SKILL.md` in the repository and the package, its hash part of the insights prompt version). It's the same call, so `analysis.maxTokens` covers it. The model is told a remark is almost always none.

| Remark | Severity | When |
|---|---|---|
| `name-contradicts` | concern | The name asks one thing and the checks or judge questions look at another |
| `judge-too-loose` | concern | A judge question passed a reply that doesn't do what the eval asks |
| `judge-leaks-answer` | concern | A judge question gives away the answer, so echoing its words passes |
| `prompt-names-tool` | concern | A choose eval's prompt names the tool it asks about |
| `name-no-intent` | suggestion | The name is a topic, a ticket id or "test 3" |
| `compound-judge` | suggestion | One judge question asks two things, and failed |

reeval checks every model remark in code and drops, without asking again, one whose kind isn't in this table, or that has no quote copied exactly from its cited line of the eval's declaration, in a file unchanged since the run. Its evidence and fix are the model's as written; one with no fix prints `at` and its quoted line. Each dropped remark is in `insights.json`'s `validation.drops` with the reason, and `reeval analyze --insights -v` lists them.

### Once per eval

Each remark prints until a run prints it: one of reeval's for that version of the eval (its sources' hashes), so an edit prints it again; a model remark while its quote is in the eval. `slow-mcp-start` and `proxy-only-egress` print in every run that has them. A remark the cap or a run without `-v` left out isn't spent: the next run that raises it prints it. Only the terminal output counts as printed; `summary.md` holds the concerns a run without `-v` prints.

The default branch is `origin`'s HEAD, else a local `main` or `master`, else, when `CI` is set, `GITHUB_BASE_REF` (a GitHub Actions pull request build has neither ref; a push build of `main` or `master` checks it out locally). Without one, `check-never-fails` isn't raised. The history kinds need an earlier run.

A run writes its remarks to `.reeval/runs/<runId>/insights.json`, in the run's [insights](/reference/cli/analyze#during-a-run) when it writes them and alone (`model: "none"`) otherwise, and `reeval record` carries the file into the history folder. `reeval show` prints them, and [`reeval analyze --insights`](/reference/cli/analyze#insights) keeps them and adds the model's.

## What it writes

| Path | What | When |
|---|---|---|
| `.reeval/runs/<runId>.json` | The scratch run's record | Every run, including one where nothing passed |
| `.reeval/runs/<runId>/` | Its artifacts | Every run |
| `.reeval/runs/<runId>/insights.json` | The run's insights and [remarks](#remarks) | `analysis.model` set, without `--no-insights`, or the run raised remarks |
| `.reeval/analysis/classify/` | Each eval's cached classification | With insights |
| The history folder | A copy of both | `--record`, `record: 'ci'` with `CI` set, or `record: 'always'` |
| `.reeval/debug/` | Each attempt's local debug data | Every run |

## Related

- [Sizing runs](/guide/profiles): profiles, `--dry-run` and budgets
- [The matrix](/concepts/matrix): how evals become cells and attempts
- [reeval record](/reference/cli/record)
