Review
cagr review reviews the branch against its base with a reviewer agent: Claude Code or Codex, started read-only in a fresh context, with its own hooks turned off. It reads the review guide, .cagr/review.md (what matters in this repo), the diff, the commit messages and the risk tier, and it can read any other file in the repo that it needs.
With review on, the review gate denies gh pr create and gh pr ready until the current commit has passed a review. The loop for an agent is:
- Commit.
- Run
cagr review. It takes a few minutes, so give the command a 15 minute timeout. - Fix each Important finding and commit, or answer it.
- Run
cagr reviewagain. After round 1 it reviews only the new commits. - Open the pull request when no Important finding is open.
The recommended preset turns review on. Without a preset, set [review] enabled = true.
Running a review
cagr review # review the branch's commits since its base
cagr review --base develop # compare with another base branch
cagr review --full # review the whole branch again, not just new commits
cagr review --effort deep # pick the effort for this run
Before it starts:
- Tracked files must be committed. If not: “Commit your changes first. The review covers commits, and the gate checks the commit you open the pull request from.”
- The base is
--base, else[git] protected_branch, elsemain. cagr usesorigin/<base>if it exists, else the local<base>. If it finds neither: “Cannot find the base branch “develop”. Fetch it, or pass –base." - If the branch has no commits beyond the base, there is nothing to review.
- If the current commit was already reviewed, it says so and does not run again, unless you pass
--full.
The diff leaves out files that match [review] skip. A diff over 150,000 characters is cut, with a note and a --stat summary. The commit log is cut at 6,000 characters.
A report looks like this:
Round 2 (diff, standard, medium risk, claude): 1 important, 1 nit.
Fixed since the last round:
- f1 src/deals.ts:42 ...
Important:
- f3 src/deals.ts:88 ...
The evidence: which input, and what happens.
Class: .... Check for the same problem elsewhere.
Nits (optional):
- f4 ...
Next: fix each Important finding, commit, and run `cagr review` again. It reviews only the new commits. ...
When nothing Important is open, it ends with “Passed. No Important finding is open, so the gate lets the pull request through.”
Exit codes: 0 passed, 1 Important findings open, 2 error (including a reviewer that could not run, and bad arguments).
With [review] status = true, cagr review and cagr review answer also set the GitHub commit status cagr/review on HEAD: success when the gate passes, failure with the count of open Important findings. A note on stderr says whether it was sent. The exit code does not change. See Commit statuses without a workflow.
Effort
Effort sets how many agents look at the change.
| Effort | Finders | Verifiers |
|---|---|---|
quick | one bug finder | none |
standard | a bug finder and a security finder | one, which drops what it cannot confirm |
deep | a bug finder, a security finder and a behavior finder | three, and a finding stays when at least two agree |
With [review] security = false, the security finder is replaced by a behavior finder in standard, and deep runs a bug finder and a behavior finder.
Which effort runs:
--effort, if you pass it.- Otherwise
deepwhen the change is high risk (see Risk tiers). - Otherwise
[review] effort(defaultstandard).
Each reviewer agent may run for [review] timeout_seconds (default 600).
Findings
Each finding has an id (f1, f2, and so on), a file and line, a title, the evidence, and sometimes a bug class so you can fix every instance.
Severities:
- Important: a real problem in this change. Only Important findings hold the gate.
- Nit: optional. At most 3 per round are kept.
- Pre-existing: a problem not caused by this branch. Optional.
Categories are bug, security, behavior, test, style and other. Style findings are dropped, since style is the linters’ job. Duplicates are merged, and a finding from an earlier round is not repeated.
Rounds
Every run of cagr review is a round. Round 1 reviews the whole branch. After that, if the last good round’s commit is an ancestor of the current one, the next round sees only the new commits and the open findings. It marks the findings that were fixed and does not repeat itself. After a rebase, the next round reviews the whole branch again.
A round that could not run is recorded with its error and does not count toward the round number.
After [review] max_rounds rounds (default 3), the report and the gate add: “Step back: if findings keep coming, the approach may be wrong. Rethink it, or answer the findings and let a person decide.”
The record for each branch lives in <git common dir>/cagr/reviews/<branch>.json, so every worktree of the repo shares it. Nothing is written to the working tree.
Answers
A finding that is wrong, intentional or minor gets an answer instead of a fix:
cagr review answer f2 --wrong "the list is never empty here: parseDeals returns at least one row"
cagr review answer f3 --intentional "we keep the old field until the mobile app ships"
cagr review answer f4 --minor "the log line only runs in dev"
- The reason must be a sentence a person can check, at least 8 characters.
- Any finding can be answered. An answered finding no longer holds the gate.
- A person reads every answer.
cagr review statusshows them.
cagr review status lists the branch’s rounds and its open, answered and fixed findings, with each answer.
Stats
cagr review stats reads the records of every branch and prints:
- how many branches were reviewed and how many passed;
- the mean rounds to pass, the share that passed in one round, and the share that took five or more;
- how many findings there were, how many were fixed, and how many were answered of each kind;
- the noise: “Noise: X% of findings were answered as wrong.”
A high noise rate means the reviewer flags things that are not problems. Tell it what to skip in the review guide.
Evals
Evals measure the reviewer on past changes with a known answer, so a change to the review guide, the model or the effort can be measured instead of guessed.
Put one TOML file per case in .cagr/review-evals/:
# .cagr/review-evals/null-deal.toml
name = "null-deal" # optional, defaults to the file name
base = "a1b2c3d" # the commit before the change
head = "e4f5a6b" # the commit with the bug
[[expect]]
file = "src/deals.ts" # where the bug is
line = 42 # optional
For a change with no bug, use clean = true instead of [[expect]] entries. A case must have one or the other.
cagr review eval # run every case
cagr review eval --case null-deal # run one case
cagr review eval --effort deep # run with another effort
Each case is checked out in a temporary worktree under .git/cagr/eval-trees/ and reviewed in full. An expected bug counts as found when an Important finding lands in its file, within 10 lines of its line (or anywhere in the file if no line is given). Any other Important finding is a false positive.
PASS null-deal: found 1/1, 0 false positives
MISS auth-redirect: found 0/1, 1 false positive
Recall 50%, false positives 1.
Last run: recall 100%, false positives 0. A drop after a REVIEW.md or model change means the change made the reviewer worse.
A run of every case is saved to .git/cagr/evals/last.json and compared with the next one. A run with --case is not saved. The command exits 2 if any case had an error.
Scanning existing code
--scan reviews existing files instead of a branch’s commits, for example code that was written before cagr:
cagr review --scan 'src/billing/**' 'src/auth/**'
cagr review --scan 'src/**' --issue
A scan reviews the tracked files that match the globs. It does not need a clean tree, does not write a record and does not count as a round. It exits 1 if it found any Important finding. With --issue, it opens a GitHub issue with the findings through gh, titled “cagr scan: N findings in
The reviewer
[review] reviewer picks the agent:
"auto"(the default): Claude Code if you are running inside Claude Code andclaudeis installed, then Codex if you are running inside Codex, then whichever ofclaudeandcodexis installed. If neither is found, the round is recorded as an error (“no reviewer agent found …”) and the review fails open."claude":claude -pin read-only mode. It may only use Read, Grep and Glob, and loads no project settings, MCP servers or hooks."codex":codex exec --sandbox read-onlywith hooks turned off.- A command: any other string runs as a shell command.
{prompt_file}becomes the path of the prompt, which is also sent on stdin, and{schema_file}the path of the JSON schema its output must match. It must print that JSON.
[review] model passes a model to claude (--model) or codex (-m).
The review guide
[review] guide is read into every review. It defaults to .cagr/review.md, or to REVIEW.md when only that file exists. It is where you say what matters in this repo and what does not. A missing file is fine. cagr init writes a starter .cagr/review.md when review is on and no guide exists:
# Review guide
`cagr review` reads this file into every review. Say what matters in this repo and what does not.
## Always check
- Errors are handled where they happen, or passed up with context. Nothing fails silently.
- New behavior has a test that would fail if the code broke.
- Inputs from users, files and the network are checked before use.
## Important here
<!-- The places where a bug costs the most: money, auth, data loss, migrations. -->
## Skip
<!-- Generated code, vendored code, snapshots. Style is left to the linters. -->
After you change it, run cagr review eval to see whether the reviewer got better or worse.
The PR gate
With [review] enabled = true:
- review-gate denies the
[review] gatecommands (by defaultgh pr createandgh pr ready) until the current commit has passed. - pr-body denies
gh pr createwhen the body lacks a[review] pr_sectionsheading. - agent-approval denies resolving review threads, and with
agents_may_merge = falsealso merging and approving.
The gate sees gh commands only. A pull request opened in the browser or through another client gets past it. Branch protection and the CI review job close that gap.
Risk tiers
The risk tier of a change is set by the files it touches, not by its author:
- Each file takes the highest tier of the
[[risk.paths]]entries it matches, or[risk] default(medium) if none match. - The change takes the highest tier of its files. An empty change is low.
| Tier | Human approvals | Review effort |
|---|---|---|
low | 0 | [review] effort |
medium | 1 | [review] effort |
high | 2 | deep, unless --effort is given |
[[risk.paths]]
glob = "src/auth/**"
tier = "high"
owners = ["@org/security"]
reason = "Sign-in and sessions."
cagr risk shows the tier of the branch’s change (against --base, else the protected branch, else main), or of files you name:
$ cagr risk src/a.ts README.md .github/workflows/x.yml
Risk: high. Needs 2 human approvals.
Why: CI workflows run with the repo's secrets.
high (1): .github/workflows/x.yml
medium (1): src/a.ts
low (1): README.md
It also lists the owners of the files. cagr does not enforce the approval counts. Set them in branch protection, and use owners to write CODEOWNERS (see CI). cagr ci reports the tier in the job summary.
Fail-open
When the reviewer cannot run (not installed, a timeout, bad output), the round is recorded as an error, cagr review prints the error and exits 2, and:
- If no finding is open, the gate lets the pull request through and tells the human: “cagr: the review of … could not run (…), so this pull request goes out unreviewed. Review it yourself before you approve it.”
- If findings from an earlier round are still open, the gate still denies.
So a broken reviewer never blocks work, and never hides findings that were already there.