> For the complete documentation index, see [llms.txt](https://docs.codna.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.codna.ai/guides/review.md).

# codna review

codna review reads a pull request, posts findings with severity, category and confidence, approves a clean diff, and names a red required check next to its verdict.

`codna review` reads a pull request when it opens, on demand, or from the command line, and posts **high-confidence inline findings**: correctness, security, and performance bugs anchored to the changed lines. When the diff is clean it approves. Reply `@codna fix` on any finding and the [GitHub App](/guides/github-app.md) opens a fix pull request for it.

The review pass is **read-only**. It does not run your code, apply patches, or hold a write token beyond the one that posts the review. The fix is a separate path, invoked only when you ask for it.

## What a finding looks like

Each finding is one inline comment on the changed line, with a severity (`HIGH`, `MEDIUM`, `LOW`), a category (`correctness`, `security`, `performance`), an explanation, and, where possible, a suggestion you can commit from the GitHub UI:

> **🔴 HIGH · security** — Timing-attack vulnerability: naive `==` replaces a constant-time compare
>
> `verify_token` compares the token with `provided == expected`, which short-circuits on the first differing byte and leaks timing usable to recover the secret.
>
> *Reply `@codna fix` and Codna opens a verified, test-green, risk-gated fix PR.*

Codna posts **one review per push** and one check named `codna review`. Findings that cannot be anchored to a changed line go in the review body. A clean review reads `**codna review** — no high-confidence issues found. ✅`

## The verdict

The review is posted as an **Approve** when it is clean at the severities that mean "do not merge this yet": no medium or high finding in this pass, and no earlier Codna medium/high thread on the pull request still unresolved. Low findings never withhold approval. Anything else is posted as a comment. Codna never requests changes.

The check summary says which, in one line:

| Line                                                                                                                                                         | When                                                                                                                              |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------- |
| `✅ Approved: no medium/high findings and no unresolved codna threads.`                                                                                       | Clean. The approval counts toward a branch rule that requires approving reviews and toward OpenSSF Scorecard's Code-Review check. |
| `⏸ Not approved: 2 medium/high finding(s) in this review; 1 earlier codna finding thread(s) at medium/high still unresolved -- resolve them once addressed.` | Resolve a thread once its finding is addressed. The next push approves.                                                           |
| `⏸ Not approved: the pull request head moved from 9f3c1a2b to 4e7d8c01 while this review ran; the current head gets a review of its own.`                    | A push landed during the review.                                                                                                  |
| ``Approval is turned off for this repository (`review.approve: false`).``                                                                                    | You set `review.approve: false` in `codna.yaml`.                                                                                  |

The App cannot approve a pull request it authored itself, so its own fix PRs get the same line as a comment, followed by `(the App cannot approve its own pull request)`.

An approval is a statement about the diff, not a merge go-ahead. When a **required** check is red on the head at the moment the review posts (a failing CI job, a failed deployment status), the verdict line is followed by one naming it, in the review body and in the check summary:

> 🔴 Heads-up: head 9f3c1a2b is red -- required check(s) failing at review time: `CI` (failure). This review's verdict is about the diff, not a merge go-ahead.

Commit statuses are visible to the App only where it has *Commit statuses: read*.

### The check

| Conclusion | When                                                                                                                                                                           |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `success`  | No findings.                                                                                                                                                                   |
| `neutral`  | Findings were posted and blocking is off (the default). Findings never fail your build unless you opt in.                                                                      |
| `failure`  | Blocking is on (`--blocking`, or `review.blocking.enabled: true`) and a finding hit a blocking severity (`high` by default), or the review ran out of time (`review_timeout`). |

## Dependency findings are checked first

A finding on a manifest or lockfile that claims a version does not exist, an integrity hash or a recorded license is wrong, a package has no downloads, an `engines` or `requires_python` constraint is violated, or a peer dependency is violated is checked against the npm or PyPI registry before it is posted. The files this applies to:

| Ecosystem | Files                                                                                                                    |
| --------- | ------------------------------------------------------------------------------------------------------------------------ |
| npm       | `package.json`, `package-lock.json`, `npm-shrinkwrap.json`, `pnpm-lock.yaml`, `yarn.lock`, `bun.lock`, `bun.lockb`       |
| PyPI      | `pyproject.toml`, `poetry.lock`, `uv.lock`, `Pipfile`, `Pipfile.lock`, `setup.py`, `setup.cfg`, `pixi.toml`, `pixi.lock` |

Runtime constraints are checked against the runtime your repository declares (`.nvmrc`, `.node-version`, `node-version:` in workflows, `FROM node:` in a Dockerfile, else `engines.node`).

* A claim the registry **contradicts** is not posted.
* A claim it **confirms** keeps its severity, and the explanation says so.
* A claim that **could not be checked** is posted as **low** and marked *Unverified*.

An optional peer dependency is never reported as a violation. At most 24 registry lookups are made per review, each with a 5-second timeout, and none are made when `privacy.egress: fail-closed` is set.

## How it works

{% stepper %}
{% step %}

#### Scope the diff

Codna reviews the changes on the pull request. Re-reviews are **incremental** by default: only the commits pushed since Codna's last review, so they stay fast and quiet. Pass `--full` (or set `review.incremental: false`) to review the whole pull request again. An explicit `--diff origin/main...HEAD`, as the GitHub Action passes, reviews that range. The diff handed to the review agent is capped at 60,000 characters; a larger diff is truncated with the note `[diff truncated for length — review what is shown]`.
{% endstep %}

{% step %}

#### Understand, then review

The review agent receives the pull-request diff, the changed-file list and your project guidance as its task, and never a request to change anything. It runs with writes and shell disabled. If its final message is not the findings JSON Codna asks for, Codna asks once more with the contract restated; a second miss fails the check with the agent's reply shown, never an empty green review.
{% endstep %}

{% step %}

#### Post findings, once

Findings are resolved to GitHub diff anchors and posted as inline comments; anything that cannot be anchored goes to the summary. A per-finding fingerprint dedups across pushes, so a force-push never respawns the same thread. Only findings at or above `--min-confidence` (default `0.75`) are posted, capped at `--max-findings` (default `10`).
{% endstep %}

{% step %}

#### Fix on request

In the GitHub App, reply `@codna fix` to a finding. Codna runs `codna fix` with that finding as the issue and opens a fix pull request stacked on the PR branch when the patch clears its risk gate. Otherwise it replies that it could not and pushes nothing. See [GitHub App § Comment commands](/guides/github-app.md#comment-commands).
{% endstep %}
{% endstepper %}

## Triggers

| Trigger                                                            | Result                                                                |
| ------------------------------------------------------------------ | --------------------------------------------------------------------- |
| CLI `codna review --pr … --post`                                   | Review the diff and post the review plus the `codna review` check.    |
| GitHub Action `mode: review`                                       | The same, from your CI runner.                                        |
| GitHub App: PR opened, synchronized, reopened, or ready for review | Automatic review. Drafts are skipped.                                 |
| GitHub App: `@codna review` on a PR                                | Review on demand. Incremental by default.                             |
| GitHub App: `@codna fix` as a reply to a Codna finding             | A fix pull request for that finding.                                  |
| GitHub App: merge queue requests checks                            | `codna review` on the group commit, inheriting the PR head's verdict. |

Install the [GitHub App](/guides/github-app.md) and the next pull request is reviewed. `@codna review` is read-only. `@codna fix` spends a metered run and pushes a branch, so it is gated to commenters with write access and acts only on a comment the Codna App authored.

## From the CLI

Review the working changes locally, or a real PR:

```bash
# Review uncommitted changes against HEAD
codna review .

# Review a diff range
codna review . --diff origin/main...HEAD

# Review a PR and post the findings + the "codna review" check
codna review --pr owner/repo#123 --post
```

`--post` needs `--pr` and a write token (`--github-token`, or `$GITHUB_TOKEN` / `$CODNA_GITHUB_TOKEN`).

| Flag                | Default          | Purpose                                                                   |
| ------------------- | ---------------- | ------------------------------------------------------------------------- |
| `--pr`              | —                | PR to review/post to: `123`, `owner/repo#123`, or a PR URL                |
| `--post`            | off              | Post one review + the `codna review` check (needs `--pr` + a write token) |
| `--diff` / `--base` | `HEAD`           | Diff range / base ref to review                                           |
| `--min-confidence`  | `0.75`           | Only report findings at or above this confidence                          |
| `--max-findings`    | `10`             | Cap the number of findings                                                |
| `--effort`          | `medium`         | Review depth: `low` (cheaper) · `medium` · `high` (more thorough)         |
| `--full`            | off              | Review the whole PR, not only commits since the last review               |
| `--blocking`        | off              | Fail the check on a blocking-severity finding                             |
| `--model`           | provider default | Review model, e.g. `openai/gpt-5`                                         |
| `--json`            | off              | Emit the review result as JSON                                            |

For the complete flag list, including the legacy `--triage` mode, see the [CLI Reference](/reference/cli.md).

## From the GitHub Action

Set `mode: review` to review the triggering PR and post findings + the check. It is read-only and holds no `contents: write` token. On `pull_request` events the action fetches the base branch and reviews `origin/<base>...HEAD`. For non-PR events, pass `diff` explicitly, for example `diff: origin/main...HEAD`.

{% code title=".github/workflows/codna-review\.yml" %}

```yaml
on:
  pull_request:
    types: [opened, synchronize, reopened]

permissions:
  contents: read
  pull-requests: write
  checks: write

jobs:
  review:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
        with:
          fetch-depth: 0
          persist-credentials: false
      - uses: thyn-ai/codna-action@3fbffb7b4f9e5d93d4b8a59eee13520698fbbe3f # v1
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
        with:
          mode: review
          model: openai/gpt-5
          effort: medium          # low | medium | high
          # min-confidence: "0.75"
          # blocking: "false"      # non-blocking by default
```

{% endcode %}

The Action runs the same `codna review` the App runs, so the approval, the red-head note, and the registry check apply to it too.

## Noise control and review policy

* **Confidence and cap.** `--min-confidence` and `--max-findings` keep the review to the findings worth a human's attention.
* **Dedup.** A line-independent fingerprint means one finding is one thread, even across force-pushes.
* **Project rules.** Codna reads your repository's review guidance (`AGENTS.md`, `.codna/review.md`, or a Cursor `BUGBOT.md` at `.cursor/BUGBOT.md`) and the `review:` block of `codna.yaml` from the PR's checked-out head, so guidance you add on a branch takes effect on that PR's review.

The `review:` block:

```yaml
review:
  enabled: true
  min_confidence: 0.75        # 0–1
  max_findings: 10
  approve: true               # false: never post an Approve
  blocking:
    enabled: false            # true: the check fails on a blocking severity
    severities: [high]        # high | medium | low
  categories:
    correctness: true
    security: true
    performance: true
  ignore_paths: []            # paths whose findings are dropped
  rules_file: .codna/review.md
  effort: medium              # low | medium | high
  incremental: true           # false: every review covers the whole PR
```

## Large pull requests and the review window

A review turn runs under a time budget that Codna sizes for the pull request in front of it. Nothing to configure: a ten-line change gets about four minutes, a thousand-line change gets about ten, and the budget learns from the reviews Codna has run.

The budget is the larger of two estimates, kept within fixed bounds:

| Estimate        | How it is computed                                                                                                                                                                                                                                                                    |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Size**        | 240 s, plus 0.35 s per changed line (added or removed, capped at 900 s), 5 s per changed file (capped at 300 s), and 2 ms per prompt token (capped at 900 s).                                                                                                                         |
| **Observation** | Every completed review turn's duration is recorded and normalised by that review's size, so small pull requests inform large ones. Codna forecasts over those durations for this pull request's size and picks the smallest budget whose probability of being exceeded is at most 2%. |

| Bound | Value                                                         |
| ----- | ------------------------------------------------------------- |
| Floor | 240 s (4 min)                                                 |
| Cap   | 1200 s (20 min), inside the GitHub App's 30-minute job window |

The cap is also the review's **deadline**. A review may run two agent turns: the review turn and, if the agent's reply was not findings JSON, one repair turn. Both share the same 20 minutes. When less than a minute is left, the repair is skipped and the check fails with `review_timeout`.

A turn that hit its budget raises the next budget for a pull request of that size by a quarter. That is what makes a retry meaningful.

When a review does run out of time, the `codna review` check fails with `review_timeout` and says what happened: the diff's size, the budget that was granted, and the two ways forward. Comment `@codna review` to retry with the grown budget, or split the pull request. No review is posted for a turn that did not finish, so an out-of-time review never approves a pull request. It is reported as a failed check, not a neutral one, so an unreviewed pull request cannot pass a merge queue that requires the check. The GitHub App retries the job on its own with the grown budget.

### Tuning

| Variable                      | Purpose                                                                             | Default                  |
| ----------------------------- | ----------------------------------------------------------------------------------- | ------------------------ |
| `CODNA_REVIEW_HISTORY_DIR`    | Directory holding the recorded review turns the forecast learns from.               | `~/.codna/review-budget` |
| `CODNA_REVIEW_BUDGET_EPSILON` | The accepted probability that a turn exceeds its budget. Smaller means more budget. | `0.02`                   |

## Security

Review and fix are separate privilege tiers. The review holds no `contents: write` token; only the gated fix step mints a scoped token to push a branch and open a pull request.

```mermaid
flowchart TD
    ev[PR opened / @codna review] --> rev[Review<br/>read-only · no write token · no shell]
    rev --> post[Post inline findings +<br/>the 'codna review' check]
    post --> reply{Reply @codna fix?<br/>needs write access +<br/>a Codna-authored finding}
    reply -->|no| done([done])
    reply -->|yes| fix[codna fix<br/>localize → patch → risk gate]
    fix -->|pass| pr[Scoped token →<br/>open fix PR on the PR branch]
    fix -->|fail| closed[Reply fail-closed ·<br/>push nothing]
```

The review treats every pull request, and a forked one in particular, as untrusted input:

* Read-only is enforced below the model: no writable filesystem, no shell or code execution, and **no GitHub write token** for the review agent.
* What leaves your machine, or the App, is the diff and your project guidance to your model provider, plus the bounded registry lookups above. See [Privacy and data](/concepts/privacy.md).

## Next steps

* [GitHub App](/guides/github-app.md) — the check-run lifecycle, comment commands, and limits.
* [CLI Reference](/reference/cli.md) — every `codna review` flag.
* [GitHub Action](/guides/github-action.md) — `fix`, `review`, and `secure` modes in CI.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.codna.ai/guides/review.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
