The claude code review command reads the diff you have not merged yet and reports correctness bugs plus reuse, simplification and efficiency cleanups, each with a file, a line and the scenario in which the code misbehaves. We wanted to know how much of a diff it actually catches, so on 2026-09-04 we planted six known defects in a 53-line change and ran it five times against Claude Code 2.1.260 — two runs at the default level, one at low, one at high, one on a cheaper model. All six were found across the five runs. Only four were found by every run.
Key takeaways
/reviewis an alias for/code-review— same command, same code path, andultrais a subcommand that routes to a cloud review.- Five runs on the same diff: 7, 5, 4, 5 and 4 findings. The union is all six planted defects; the intersection is four.
- The two flaky ones are the quiet kinds: an unguarded null dereference (3 of 5) and an O(n·m) loop (1 of 5).
- The effort word is not applied directly. A routing table maps model plus level to a prompt, and on Claude Opus 5
mediumandhighresolve to the same cell. - With no level given it reuses the level you typed last and tells you so — which makes a bare
/code-reviewa different command depending on your history.
The short answer
Run it on the diff before you open the pull request, and run it twice on anything you care about — the second run on our diff found two things the first one did not, and vice versa. It sits in the same surface the complete Claude Code guide maps, it is catalogued in Anthropic's commands reference, and the flags below are in the CLI reference. Read the findings as a list of things to check rather than a list of facts.
claude -p "/code-review" --output-format json # → 58s, $0.1088, 7 findings on a 53-line diff # → each one: file, line, summary, failure scenario

/review and /code-review are one command
The registration in the 2.1.260 binary settles it:
{name:"code-review",aliases:["review"],
menuDescription:"Review the current diff or a PR for bugs and cleanups",
subcommands:{ultra:"ultrareview"},
argumentHint:"[low|medium|high|xhigh|max|ultra] [--fix] [--comment] [<pr#>|<branch>|<path>]"}
So /review and /code-review are the same thing, /review ultra hands off to the cloud multi-agent review rather than running locally, and the argument grammar is one optional level, two optional flags and one optional target. We ran both names against the same diff and got the same defects back, which is what an alias should do and is worth ten seconds to confirm rather than assume.
Three flags matter. --fix applies the findings to the working tree after reporting them, skipping anything whose fix would change intended behaviour or reach well outside the diff. --comment posts each finding as an inline PR comment through the GitHub MCP tool, falling back to gh api and then to printing. --post is ultra-only and posts the finished review to a GitHub PR as a single comment.
The six defects we planted
The corpus is a two-file, 53-line diff on a branch: cart maths and order handling, the kind of change that goes through review on a Tuesday. Every defect is a real one we have seen in production code, and none of them is signposted in a comment.
| # | File | The defect |
|---|---|---|
| 1 | src/cart.js:3 | Loop bound <= reads one element past the end |
| 2 | src/cart.js:13 | applyDiscount and applyCoupon are byte-identical |
| 3 | src/cart.js:23 | includes() inside filter() — O(items × skus) |
| 4 | src/orders.js:8 | Missing await on the archive write |
| 5 | src/orders.js:13 | Refund window inverted against the README |
| 6 | src/orders.js:17 | order.customer dereferenced with no guard |
Two of those are correctness bugs that crash (1 and 6), two are correctness bugs that silently do the wrong thing (4 and 5), one is a duplication cleanup (2) and one is an efficiency cleanup (3). That spread is deliberate: the command's own description promises "correctness bugs and reuse/simplification/efficiency cleanups", so the corpus tests both halves of the claim.
What five runs found
Every row is one process, one diff, nothing changed in between.
| Run | Level | Model | Time | Cost | Reported | Planted found |
|---|---|---|---|---|---|---|
| 1 | default | sonnet | 58s | $0.1088 | 7 | 6 of 6 |
| 2 | default | sonnet | 57s | $0.0968 | 5 | 5 of 6 |
| 3 | low | sonnet | 12s | $0.0858 | 4 | 4 of 6 |
| 4 | high | sonnet | 99s | $0.1906 | 5 | 5 of 6 |
| 5 | default | haiku | 51s | $0.0440 | 4 | 4 of 6 |
Four defects were found by all five runs: the off-by-one, the duplicated function, the missing await and the inverted refund window. Those are the ones with a shape — a changed operator, two identical bodies, an un-awaited promise in an async function.
Two were not. The unguarded order.customer dereference came back in three runs of five. The O(n × m) includes() came back in one run of five — and the low run explained why, saying the finding was "a perf/efficiency concern outside this review level's scope". That is a defensible reading of low, and it is also the single most common real-world defect class in the corpus.
More effort did not mean more findings. The high run took 8× as long as low and cost 2.2× as much, and reported one more finding than low and two fewer than the cheaper default run before it. On this diff, effort bought consistency in the four easy findings and nothing else.
The effort word you type is not always the review you get
/code-review accepts low, medium, high, xhigh, max and ultra. What it does with that word is less direct than it looks. Inside the binary there is a frozen routing table keyed by model and then by level, and each cell names the prompt that actually runs:
"claude-opus-5": { low: cell "low",
medium:cell "o5-bmin",
high: cell "o5-bmin", ← the same cell as medium
xhigh: cell "o48-xhigh-v1",
max: cell "max" }
"claude-sonnet-5":{ low: cell "low-sonnet5", modelEffort "medium",
high: cell "high", finderBudgetHint true, … }
Two consequences, both read straight out of the object. On Claude Opus 5, medium and high resolve to the same prompt, so typing the longer word changes nothing about the review. On Claude Sonnet 5, low runs the model at medium effort rather than low — the level names the prompt, not the thinking budget, and the two do not always agree.
And when you type no level at all, the command reaches for your history:
No effort level given — reusing ${last}, the level the user typed last time
${last !== effective ? `; running at ${effective} here` : ""}.
That is stored as codeReviewLastEffort and it is why a bare /code-review is not a stable command: it means whatever you last typed, on whatever model you are on now. The message is careful enough to distinguish the level it remembered from the level it is actually running, which is the tell that those two things can differ.
How findings leave the session
The review reports through a tool rather than prose, and then repeats itself on purpose. The prompt's own instruction:
…a comment that states the issue and the concrete scenario in which the code misbehaves. Quality over quantity: include everything you genuinely believe is a real issue, and nothing you don't. After the tool call, also restate the findings in your final reply one line each, `file:line summary`, so they stay visible in sessions that do not render tool output.
That second paragraph is why claude -p "/code-review" works at all. In a print-mode session there is no UI to render a findings panel, so the text restatement is the entire output — and in four of our five runs it arrived as a fenced JSON array with file, line, summary and failure_scenario per finding, which is directly parseable. The low run returned a markdown bullet list instead. Do not write a CI step that assumes the JSON block is there.
The same print-mode plumbing is what makes the command scriptable at all; the CLI reference covers --output-format and the rest of the envelope. There is also a follow-up contract: if findings are fixed later in the same session, the model is required to call the reporting tool again with an outcome of fixed, no_change_needed or skipped per finding, because that is the only thing that updates their status in the host UI. If you use --fix, that second call is where the "what got skipped and why" summary comes from.
Running the claude code review command in CI
The claude code review command runs headless, which makes it a plausible pre-merge gate. Three things to get right:
- Name the level explicitly. Otherwise the depth depends on
codeReviewLastEffort, which is not in your repository. - Use
--output-format jsonso you gettotal_cost_usdandnum_turnsnext to the findings, and can put a ceiling on both. - Do not gate on exit code. A review that finds nothing and a review that could not read the diff both exit 0. Gate on the presence and shape of the findings, and fail the job if the reply parses to zero findings and the diff was non-empty.
claude -p "/code-review high" --output-format json > review.json # → parse .result for the findings block; .total_cost_usd for the bill
Budget 50–100 seconds for a diff this size on sonnet and about a tenth of a dollar. Cost scales with the diff, not with the repository — the review reads the changed hunks and the functions around them, not the tree. If you are comparing this against the rest of the field, our survey of AI code review tools covers what the hosted services do differently.
What it will not do
It is not a security review — and it will still out-report one. We pointed it at the second corpus from the security review measurement: a 34-line diff with six planted vulnerabilities. In 90 seconds for $0.2064 it reported all six plus a seventh, including the committed API key and the unsalted MD5 hash that /security-review on the same model left out of two runs. That is not a win. /code-review has no threat model, no exclusion list and no per-finding verification pass, so it says more because nothing is filtering it — every finding arrives at the same confidence as the loop bound above. The security command exists to be quiet, and being quiet is what makes its output actionable.
It does not run your code. Every finding is derived from reading. The prompt has a matching honesty about this: after reporting, it may hand off to a bundled verification skill precisely because "this review checks that the diff reads right" and something else has to check that it runs right. If you want something that runs on every change rather than when you remember to ask, that is a hook — and PostToolBatch rather than PostToolUse, which never fires when a tool fails.
It does not know your intent. The refund-window defect was caught because the README states the policy in one sentence. Remove that sentence and an inverted comparison is just a comparison. Everything the review knows about what your code is supposed to do, it reads out of your repository — which is the same reason what /init writes is worth getting right.
What did not work
Our first corpus told the reviewer the answers. The initial version of the diff labelled each defect in a comment — // D1: off-by-one — <= walks one past the end. The review found all six, and one run quoted our own label back at us: "removeSkus … was noted in-diff as D6". Those numbers were meaningless as a detection measurement and were thrown away. The corpus in this article carries no comments at all, and every figure above comes from the rebuilt one. If you are benchmarking a review tool, the leak is easy to introduce and invisible in the output.
num_turns is zero on every run. The result envelope reports num_turns: 0 for /code-review in print mode while plainly having done several turns of work, so we could not use it as a proxy for depth and fell back to wall-clock time and cost. We do not know the mechanism and are not guessing at one.
The envelope's own timing disagrees with the clock. duration_ms came back lower than the measured wall time on every fan-out run — by three minutes on one of them. Every time in this article is wall-clock, measured by the shell around the process.
Best practices
- Say the level out loud.
/code-review high, not/code-review, so the run does not inherit state you cannot see. - Run it twice on anything that matters. Two runs of the same command on the same diff found different subsets of the same six defects.
- Read it as a checklist, not a verdict. The union of five runs was complete; no single run was.
- Keep the diff small. The review reads the change and its neighbourhood; a 2,000-line diff dilutes attention in exactly the way a human reviewer's does.
- Pair it with something that runs the code. The command says so itself, and the two flaky findings in our corpus were both things a test would have caught instantly.
Common mistakes
- Assuming
highis always deeper thanmedium. On Claude Opus 5 they are the same prompt cell. - Gating CI on the exit code. Zero findings and no diff to read look identical from outside.
- Parsing the JSON block unconditionally. One of our five runs answered in markdown bullets instead.
- Labelling test fixtures with the bug you planted. We did this and had to rebuild the corpus.
- Using it as your security gate. Different command, different prompt, different failure mode — and the security one can return an empty report that still exits 0.
Conclusion
Use /code-review high before you open a pull request, twice on anything you would be embarrassed to ship, and read the output as a set of leads rather than a finished audit — five runs against six known defects found all of them collectively and four of them reliably, and the two that slipped were the quiet ones. Then run the other command: what /security-review catches and what it refuses to report is a different measurement with a much sharper edge, and the two together cover more than either does twice.
Frequently asked questions
What does the Claude Code review command do?
Is /review the same as /code-review?
What effort levels does /code-review take?
Does /code-review catch security bugs?
Can I run /code-review in a script or CI?
Muhammad Kashif
Founder and editor of Devventa, covering AI coding assistants, Next.js and the modern AI development stack.



