> ## Documentation Index
> Fetch the complete documentation index at: https://docs.iriscode.co/llms.txt
> Use this file to discover all available pages before exploring further.

# GitHub Actions: Block Merges With Iris Code CI Enforcement

> Run Iris Code on every push and pull request. Failing lines get annotated on the diff and the merge is blocked.

A hook protects your machine. CI protects the branch, including from everyone who hasn't installed the hook.

Add Iris Code to GitHub Actions and every push and pull request gets checked. When something falls below your threshold the workflow fails, the merge is blocked, and the offending lines get annotated right on the diff so nobody has to open a log.

It runs on your own runners. Your code never leaves your infrastructure.

## The workflow

Drop this in `.github/workflows/iris.yml`:

```yaml theme={null}
name: Iris Code

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

permissions:
  contents: read

jobs:
  iris:
    name: iris
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v5

      - uses: Daiveedjay/iris-code-action@v1
        with:
          licence-token: ${{ secrets.IRIS_LICENCE_TOKEN }}
```

The action installs the CLI and runs `iris gate . --format github`. Pinning `@v1` means fixes to the invocation reach you without editing anything.

<Tip>
  Add `IRIS_LICENCE_TOKEN` under **Settings → Secrets and variables → Actions**. Directory scans need Pro. Without a token the step runs as Free and skips the directory check rather than failing your build, so a missing secret never turns the pipeline red for the wrong reason.
</Tip>

### Inputs

| Input               | Default  | Description                                                                              |
| ------------------- | -------- | ---------------------------------------------------------------------------------------- |
| `command`           | `gate`   | `gate`, `check`, `secrets`, `security`, or `cve`                                         |
| `path`              | `.`      | Directory or file to analyse                                                             |
| `format`            | `github` | `github`, `pretty`, or `json`. `secrets` and `security` support `pretty` and `json` only |
| `output`            |          | Write the JSON result to this file. Requires `format: json`                              |
| `min-score`         |          | Minimum health score, 0-100, for `command: check`                                        |
| `severity`          |          | Minimum severity to fail on for `command: cve`                                           |
| `licence-token`     |          | Iris Code Pro licence key                                                                |
| `slack-webhook`     |          | Slack incoming webhook                                                                   |
| `cli-version`       | `^1`     | Version range of `@iris-code/cli` to install                                             |
| `node-version`      | `20`     | Node version used to run the CLI                                                         |
| `working-directory` | `.`      | Run from here if your project is not at the repository root                              |

The action also sets an `exit-code` output, matching the codes in the table further down.

There is deliberately no general `threshold` input. Gate thresholds live in `.irisconfig.json`, which is committed and reviewable, so a workflow input that quietly overrode them would defeat the point of having a written policy. `command: gate` rejects `min-score` rather than accepting it and enforcing something you did not ask for. `min-score` applies to `command: check`, where the CLI supports `--min-score`.

## Running the CLI directly

The action is a wrapper. If you would rather see the exact commands, or pin your own CLI version, call it yourself:

```yaml theme={null}
      - uses: actions/setup-node@v5
        with:
          node-version: '20'

      - name: Install Iris Code CLI
        run: npm install -g @iris-code/cli

      - name: Run Iris Code enforcement gate
        run: iris gate . --format github
        env:
          IRIS_LICENCE_TOKEN: ${{ secrets.IRIS_LICENCE_TOKEN }}
```

**Iris Code: Add GitHub Actions Workflow** writes this version of the file for you, filled in with your current threshold.

## Configuring the threshold

A `.irisconfig.json` at your project root:

```json theme={null}
{
  "minHealthScore": 75,
  "ignoreFiles": ["**/*.test.ts", "**/generated/**"]
}
```

No config file means a threshold of 70.

## Annotations on the diff

`--format github` is what makes findings appear on the pull request itself, in red, on the exact line. No log-scraping, no extra action. It also writes a pass/fail table into the run's job summary.

It works on `iris check` too, not just `iris gate`.

## Saving a machine-readable report

To hang onto a machine-readable report whether the gate passed or failed, add two steps after the gate:

```yaml theme={null}
      - name: Save JSON report
        if: always()
        run: iris gate . --format json --output iris-report.json
        env:
          IRIS_LICENCE_TOKEN: ${{ secrets.IRIS_LICENCE_TOKEN }}

      - uses: actions/upload-artifact@v5
        if: always()
        with:
          name: iris-report
          path: iris-report.json
```

`if: always()` is what makes the report available after a failed gate, which is when it is most useful.

## Slack notifications

Add an `IRIS_SLACK_WEBHOOK` secret and the result posts to Slack as well as failing the build. See [Slack notifications](/enforcement/slack-notifications).

## Exit codes

| Code | Meaning                          |
| ---- | -------------------------------- |
| `0`  | Passed, workflow continues       |
| `1`  | Failed, merge blocked            |
| `2`  | Bad arguments or a broken config |

<Note>
  Not on GitHub? **Iris Code: Add CI Pipeline Snippet** generates the equivalent for GitLab CI, Bitbucket Pipelines, an npm script, or a plain shell step. Inline annotations are GitHub-only; everywhere else the exit code does the work.
</Note>
