---
title: Verdicts and exit codes
description: Every status, what it means, and exactly how it maps to a CI exit code — enough to write a build script against without reading source.
order: 20
---

# Verdicts and exit codes

The page to read when you're writing a script that has to branch on an Izri result.

## Run statuses

| Status | Terminal | Meaning |
| --- | :-: | --- |
| `PENDING` | | Queued, not yet picked up. |
| `RUNNING` | | Executing. |
| `PASSED` | ✓ | Everything passed. |
| `FAILED` | ✓ | Tests failed. |
| `ERROR` | ✓ | The run itself broke. |
| `CANCELLED` | ✓ | Stopped before completion. |

## Umbrella states

`izri/quality` resolves to exactly three values:

| State | Meaning |
| --- | --- |
| `passing` | No hard rule fired, composite ≥ 70. |
| `failing` | A hard rule fired, or composite < 70. |
| `unknown` | Not enough signal — delta in flight, or all children skipped. |

## Child states

| State | Meaning |
| --- | --- |
| `passing` | Ran, nothing blocking. |
| `failing` | Ran, found something. |
| `pending` | Queued or running. |
| `error` | The analyzer crashed. Hard rule — fails the umbrella. |
| `skipped` | Not applicable, or not on your plan. |

## CLI exit codes

The contract is one sentence: **`izri run --watch` exits non-zero on any terminal status except `PASSED`.**

| Terminal status | Exit code |
| --- | :-: |
| `PASSED` | `0` |
| `FAILED` | non-zero |
| `ERROR` | non-zero |
| `CANCELLED` | non-zero |

`--watch` polls every 3 seconds with a **30-minute cap**. Hitting the cap is also non-zero — treat a timeout as a failure, because a run you never saw finish is not a run that passed.

Without `--watch`, `izri run` returns as soon as the run is enqueued and exits `0` on successful submission. That exit code says the run *started*, not that it passed. Scripts that gate on quality must use `--watch`.

## Action exit behavior

`fail-on-finding` (default `true`) makes the step exit non-zero when the scope check reports hard findings or the test run doesn't pass.

Set it to `false` to surface results without failing the build. The check run and sticky comment still publish — only the workflow step's exit code changes.

## GitHub check conclusions

| Umbrella | Check conclusion |
| --- | --- |
| `passing` | success |
| `failing` | failure |
| `unknown` | neutral / in progress |

Branch protection treats a required check as unsatisfied until it concludes successfully, so `unknown` blocks a merge without marking it failed — the correct behavior for "still deciding."

Require `izri/quality`, not the children. The umbrella handles skipped signals; requiring `izri/visual` directly blocks every backend-only PR.

## Branching in a shell script

```bash
#!/usr/bin/env bash
set -euo pipefail

if izri run --watch; then
  echo "Safe to merge."
else
  status=$?
  echo "Izri verdict is not passing (exit $status)"
  izri status --json > izri-report.json
  exit "$status"
fi
```

Note `set -e` and the explicit `if` — under `set -e` a bare `izri run --watch` aborts the script before you can capture anything useful.

## Distinguishing the two failure modes

A failing umbrella is always one of two things:

1. **A hard rule fired** — `pr_type_forbidden_category`, `change_uncovered`, or an analyzer error.
2. **The composite came in under 70.**

`izri status --json` reports `hardFindingsCount`. Non-zero means case 1; zero means case 2. They call for different responses — a hard rule needs the specific problem fixed, a low composite usually needs better test coverage of the diff.

## Related

- [Signals overview](/docs/signals/overview) — weights and hard rules.
- [CLI](/docs/install/cli)
- [Troubleshooting](/docs/reference/troubleshooting)
