---
title: GitHub Action
description: The recommended install path — every input, every output, and how to make the verdict block a merge.
order: 10
---

# GitHub Action

The Action installs `@izri/cli`, runs the scope check, optionally triggers a test run, and reports the result. It is a composite action — no JavaScript bundle, just bash — so it adds a few seconds to a workflow, not a build step.

```yaml
name: izri
on:
  pull_request:
    branches: [main]

jobs:
  izri:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - uses: legendify-dev/izri/apps/github-action@v1
        with:
          api-token: ${{ secrets.IZRI_API_TOKEN }}
```

## `fetch-depth: 0` is required

The scope check diffs your head against `origin/<base>`. A shallow clone — the `actions/checkout` default — has no base ref to resolve, so the check fails before it starts. Either set `fetch-depth: 0` or fetch the base ref explicitly.

## Inputs

| Input | Required | Default | What it does |
| --- | --- | --- | --- |
| `api-token` | yes | — | Your Izri API token with the `tests:run` and `tests:read` scopes. Starts with `izri_`. Store it as a repository secret, never inline. |
| `api-url` | no | `https://api.izri.ai` | Override for self-hosted installs. |
| `project-id` | no | *(auto)* | Project ULID. When omitted the CLI resolves it from your git `origin` remote. |
| `mode` | no | `both` | `scope`, `tests`, `both`, `upload`, or `verify`. |
| `junit-file` | with `upload` | — | JUnit XML your test step produced. |
| `coverage-file` | no | — | LCOV or Cobertura report, for diff coverage. |
| `coverage-format` | no | *(from extension)* | `lcov` or `cobertura`. |
| `skipped-reason` | no | — | With `upload`, records an explicit empty run instead of results. |
| `test-command` | with `verify` | — | Shell command that runs `{files}` (the selected test files, shell-quoted) and writes JUnit XML to `{junit}`. |
| `max-test-files` | no | `50` | With `verify`: budget. Over it only the PR's changed test files run; still over, a budget skip is recorded. |
| `base-ref` | no | *(auto)* | Git base to diff against. Resolves to `origin/${{ github.base_ref }}` on pull requests and `origin/<default-branch>` on pushes. |
| `wait-for-results` | no | `true` | When `mode` includes tests, block until the run reaches a terminal status. |
| `fail-on-finding` | no | `true` | Exit non-zero on hard scope findings or a non-passing test run. |
| `cli-version` | no | `latest` | Version range for `@izri/cli`. Pin to a tag for reproducible CI. |

### Choosing a `mode`

- **`scope`** is deterministic and fast. No test run, no waiting. Good as a required check on every PR.
- **`tests`** triggers a run on Izri's runner and (by default) waits for it. Playwright projects only.
- **`upload`** sends the JUnit and coverage your own test step produced. This is the usual choice: most teams already run tests in CI, and Izri judges that run instead of repeating it. Give the job `permissions: id-token: write` (and `contents: read`, since naming any permission drops the defaults): the CLI sends the job's id-token with the upload, and that is what lets Izri trust the evidence. Without it the upload is recorded as untrusted. See [`izri/tests`](/docs/signals/tests#where-the-evidence-comes-from).

  ```yaml
  jobs:
    tests:
      runs-on: ubuntu-latest
      permissions:
        contents: read
        id-token: write
      steps:
        - uses: actions/checkout@v4
          with:
            fetch-depth: 0
        - run: pnpm test -- --reporter=junit --outputFile=junit.xml
          continue-on-error: true
        - uses: legendify-dev/izri/apps/github-action@v1
          with:
            api-token: ${{ secrets.IZRI_API_TOKEN }}
            mode: upload
            junit-file: junit.xml
  ```
- **`verify`** runs [the revert check](/docs/signals/hallucination#the-revert-check) with [`izri verify`](/docs/install/cli#izri-verify). It runs the test files that depend on the change at head, and again with the changed source files restored to the merge base, each in a throwaway git worktree. Then it uploads both results for `izri/hallucination`. They are judged against the `izri/tests` evidence for the same commit, so add it next to an `upload` step (or keep the Izri runner). Install dependencies first, and give the job the same `id-token: write` permission.

  ```yaml
        - run: pnpm install --frozen-lockfile
        - uses: legendify-dev/izri/apps/github-action@v1
          with:
            api-token: ${{ secrets.IZRI_API_TOKEN }}
            mode: verify
            test-command: pnpm vitest run {files} --reporter=junit --outputFile={junit}
  ```
- **`both`** is the default and what you want unless you have a reason otherwise.

### Pin `cli-version` for reproducibility

`latest` means a CLI release can change your CI behavior without a commit in your repository. Pin it once you're past evaluation:

```yaml
with:
  api-token: ${{ secrets.IZRI_API_TOKEN }}
  cli-version: '0.1.0'
```

## Outputs

| Output | Value |
| --- | --- |
| `scope-alignment-score` | Alignment score 0–100. Empty string when `mode=tests`. |
| `scope-findings-count` | Total scope findings, all severities. Empty string when `mode=tests`. |
| `scope-hard-findings-count` | Findings at `severity: error` — the only category that blocks by default. |
| `test-run-id` | The Izri test-run identifier. Empty string when `mode=scope`. |
| `verification-run-id` | With `verify`: the recorded revert check. Empty string otherwise, or when it was not recorded. |

Outputs are empty strings, not zeros, when their mode didn't run. Test for emptiness before doing arithmetic:

```yaml
      - uses: legendify-dev/izri/apps/github-action@v1
        id: izri
        with:
          api-token: ${{ secrets.IZRI_API_TOKEN }}

      - name: Comment when alignment is low
        if: steps.izri.outputs.scope-alignment-score != '' &&
            fromJSON(steps.izri.outputs.scope-alignment-score) < 60
        run: echo "Scope alignment is ${{ steps.izri.outputs.scope-alignment-score }}"
```

## Making the verdict binding

Until you require it, the check reports but enforces nothing.

**Settings → Branches → Branch protection rules → Require status checks to pass**, then add `Izri` (the single check run whose conclusion is the `izri/quality` verdict).

There is only one check to require. `Izri` carries the aggregated `izri/quality` verdict, which already handles skipped signals — visual legitimately skips when no UI route changed, and the verdict accounts for that. If you previously required `izri/quality` (or any `izri/…` child check), replace it with `Izri`; those contexts are no longer reported and a rule pinning them would block every merge.

## Non-blocking evaluation mode

To surface findings without failing anyone's build while you evaluate:

```yaml
with:
  api-token: ${{ secrets.IZRI_API_TOKEN }}
  fail-on-finding: false
```

The check still publishes and the sticky comment still appears — the workflow just won't go red.

## Related

- [CLI](/docs/install/cli) — the same checks from your terminal.
- [Verdicts and exit codes](/docs/reference/verdicts-and-exit-codes) — what the Action's exit status means.
- [`.izri/config.yml`](/docs/configure/config-yml) — tune what the scope signal considers sensitive.
