Review

AI code review using guides from init / sync. Progress and diagnostics go to stderr. The result goes to stdout (human text or JSON with --json).

This page helps you pick a mode. Commands, flags, and checklists live on the linked pages below.

Which mode should I use?

Start with what you are trying to review, not the flag name.

flowchart TD start([What are you reviewing?]) start --> q1{GitHub pull request
by number?} q1 -->|yes| pr[PR review] q1 -->|no| q2{Uncommitted or local
commits in a clone?} q2 -->|not yet| clone[Clone the repo and make your changes] q2 -->|yes| q3{Use shared guides
on serve?} q3 -->|no, init locally| local[Local review] q3 -->|yes, team context| remote[Remote review]
If this sounds like you Mode Read next
You are in a clone and want feedback on your branch or working tree before you open a PR Local review Your AI provider on your machine, guides on disk
Same as local review, but the repo is already inited on serve and you skip per-laptop init/sync Remote review Shared context on the server, diff from your clone
You have a PR number and do not need to check out that branch locally PR review gh only, guides on disk, AI on your machine
You push straight to main with no pull request Commit watch The server reviews each push, from the GitHub App or CI (review --remote --range)

PR review and remote review do not combine: --remote is only for a local diff with no PR positional arguments.

How the three modes differ

Same underlying guides from init / sync, different subject (what gets reviewed) and who keeps that context up to date.

flowchart TB subgraph context["Guide context"] disk["Per laptop: init / sync"] shared["Per repo on serve: init / sync once"] end subgraph subject["What gets reviewed"] direction LR diff["Git diff in your clone"] pr["PR metadata and patch via gh"] end diff --> localN["Local review"] diff --> remoteN["Remote review"] pr --> prN["PR review"] disk --> localN disk --> prN shared --> remoteN
Local review PR review Remote review
Typical moment Pre-push in your clone, you own local guides Open PR by number, you own local guides Pre-push using the team's shared server context
Who runs init/sync You on the laptop You on the laptop Ops on the server (you usually do not)
Guides at review time Laptop cache Laptop cache Server dashboard repo
Extra setup AI provider gh auth login and an AI provider serve, token, set --remote-host
Deep dive local-review local-pr-review remote-review

Team-wide automatic PR reviews on webhooks are not a fourth review mode. They go through serve and the GitHub App, and a review policy decides which pull requests they review. Use PR review when you run the CLI against one PR. To run a remote review on every pull request from a workflow, use the GitHub Action.

Depth: --improve-matrix

Reviews share one depth control, --improve-matrix=N. 1 is the default. Each step up adds another pass that audits the draft against the complete diff and the guides, and raises the output budget so a longer answer fits. The pass preserves valid findings, corrects inaccurate ones, removes duplicates, and adds what the first pass missed. More passes cost more tokens and take longer. 2 is a reasonable step up when a change is high risk or the repository is large. The value is capped at 4. See Cost for what a pass costs.

Repository review instructions

A repository can carry its own review instructions. They are appended to the reviewer's built-in system prompt as a CUSTOM REVIEW INSTRUCTIONS section, so the built-in role, the diagram rules and the guide are always kept. They are set per repository, never globally:

co-maintainer set --repo=owner/repo --review-system-prompt='Prefer the project helpers over new dependencies.'
co-maintainer set --repo=owner/repo --review-system-prompt-max-chars=20000
co-maintainer set --repo=owner/repo --unset=review-system-prompt

The dashboard's repository Settings has the same field. A blank value turns it off. See Configuration for where it is stored.

For one run, without saving anything, two review flags add to or replace that section. Both work in every mode, PR, local and remote:

Flag Effect
--system-prompt-file=PATH Append the file's contents to the repository's instructions for this run
--system-prompt-override-file=PATH Replace the repository's instructions with the file for this run. The built-in instructions are kept
co-maintainer review owner/repo 42 --system-prompt-file=extra.md
co-maintainer review --system-prompt-override-file=strict.md

The cap is reviewSystemPromptMaxChars, per repository, in characters. It defaults to 8000, and 0 or -1 means no limit. The cap applies to the stored value and to both per-run files, and set and the dashboard refuse a value over it. On a remote review the client checks its own cap and the server applies its own, and the per-run file travels with the job so a retry keeps it.

Exit codes

Code Meaning
0 Review finished, no open blocking findings
1 Review finished, at least one blocking finding
2 Usage or precondition error (init missing, not a git repo, etc.)
3 Runtime failure or abort

What counts as blocking

reviewBlocking in config.json decides it, and the same rule is used by local, PR, remote and the automatic GitHub App reviews.

Value A finding blocks when
model (default) The model says so. A P0 always blocks, even if the model labeled it otherwise.
severity Its severity is P0 or P1. P2 and P3 never block, whatever the model said.

The default keeps the 0.4.13 behavior. severity exists because the model's own label can move between two reviews of the same code, which flipped the exit code for the same finding. With severity the same finding set always yields the same exit code, the same blocking count in --json, and the same GitHub review type.

co-maintainer config set --review-blocking=severity
co-maintainer config set --review-blocking=model      # back to the default

CO_MAINTAINER_REVIEW_BLOCKING overrides it for one environment.

The automatic GitHub App review picks its type from the open findings only. With none open, including a re-review whose earlier findings were all fixed, it approves. With open findings, model requests changes, and severity requests changes for a P0 or P1 and comments otherwise.

JSON output

With --json, stdout is a single JSON object (schemaVersion 1). Fields include mode (local, remote, or pr), subject, summary, findings[], warnings[], usage, and durationMs. Local and remote runs also include diff metadata and codegraph state. Errors with --json are JSON on stdout as well.

Flag details per mode: Local review, Remote review, PR review.

GitHub output

--output=github prints the findings as GitHub workflow annotations and writes a table to the job summary, for a run inside GitHub Actions. It works in all three modes and cannot be combined with --json.

  • Each finding that is still open becomes one command on stdout, ::error when it blocks and ::warning when it does not, with its file, line span and a short title. A finding closed since the last review is not annotated.
  • When GITHUB_STEP_SUMMARY is set, a table of every finding, the cost and the time is appended to it. An unknown cost reads cost unknown with the reason.
  • The exit code is the same as without the flag, so a blocking finding fails the step.
  • Nothing else goes to stdout in this mode. Every finding is one escaped line, so text from a pull request can never start a workflow command of its own.

GitHub shows at most ten errors and ten warnings per step. When there are more, one notice says how many were left out, and the job summary lists all of them. CI shows it inside a workflow.

Human output

Without --json, all three modes print the same format. Only the header names the mode, because the subject differs:

co-maintainer review · owner/repo · my-branch
4 files · +12 −3 · guide built 2026-09-15 · codegraph used

New (1)
  • src/app.ts:16-18  [P1 · blocking] the gate ignores its threshold
    A violation exactly at the threshold passes.

Still open (1)
  • src/util.ts:4  [P2 · non-blocking] the helper ignores its argument

Closed (1)
  ✓ src/old.ts:9  [P2 · non-blocking] the dead branch

Summary: 1 new · 1 open · 1 closed · 2 blocking

Findings are grouped Closed, Still open, New, each labeled with the same blocking decision the exit code uses. The header names the real guide build date (guide unknown only when no guide timestamp can be read) and the state the run actually reached: codegraph used, codegraph disabled, or codegraph unavailable: the last one when codegraph was requested but could not be prepared, which is also what --json reports.

The severity legend and the "If you'd like me to explain it in more detail, please ask." sentence are not in terminal output: a terminal has nobody to ask, and the legend is fixed noise. Both stay in the GitHub review comment, which is a different reader.

How findings are produced

The model is asked for a JSON object, not for prose. For each finding it returns the severity, whether it blocks, the path and line span, the symbol, a title, the explanation, and an optional replacement. co-maintainer renders the Markdown from that object, so a finding's boundaries are never guessed from the model's wording. The JSON schema is sent as structured output in the provider's own format when the provider accepts it. A provider that rejects the combination gets one request without the schema, and the prompt also asks for a fenced JSON block.

If the answer is not usable JSON, the request is retried once and then, if it still fails, the older Markdown parser reads it. That parser accepts the em dash or : as the heading separator, so answers written before this change (and carry-over records saved by 0.4.13) keep working. The --json output and the human output are both built from the same parsed findings, so they can never disagree.

Before any review

Check Why
init or sync on your laptop Required for local and PR review
An AI provider for local and PR review, with both models saved AI runs on your machine, and no model is picked for you
Remote setup for --remote Host, token, repo already inited on the server