Second-opinion code and plan reviews from Codex, Claude Code, opencode, or the OpenAI API — one CLI
Project description
rocket-review
rr — a second opinion on your code, from a model that didn't write it.
v0.1 — experimental. Interfaces and flags may change. Read Security & data flow before pointing it at anything sensitive.
One small CLI that sends your plan, diff, commit, or PR to an agentic reviewer (Codex CLI, Claude Code, or opencode) that explores your project before judging — then gives you prose or structured JSON you can gate CI on.
Single-model local review is built into the vendor CLIs now (codex review,
Claude Code's /code-review) — rr exists for what a single vendor can't give
you: a second opinion from a different vendor's model, in one command, from any
shell, editor, agent, or CI.
rr is deliberately not a PR bot. It reviews before you push — the point is
that the issues get fixed before a PR exists. It posts nothing anywhere; if you
want PR comments, pipe the --json envelope into whatever posts them.
What a review looks like
Reviewing an uncommitted diff that quietly breaks a billing invariant
(rr --diff, abridged):
[HIGH] billing.py — `split_evenly` no longer preserves the input total
> The documented contract says the returned list "always sums to exactly
> `total_cents`," but the new implementation rounds one share and repeats it.
> For inputs like `split_evenly(100, 3)` it returns `[33, 33, 33]`, summing to 99.
> Suggested fix:
base = total_cents // n
parts = [base] * n
for i in range(total_cents - base * n):
parts[i] += 1
return parts
- Change Assessment: Do not merge
- Top Issues: The changed implementation loses cents and breaks the documented
remainder allocation contract.
The reviewer read the function's docstring and reasoned about its contract before flagging the regression — not a pattern match on the diff.
Why
- Cross-vendor review — the model that wrote the code shouldn't be the only one
grading it: a model reviewing its own output inherits its own blind spots, and a
vendor's built-in reviewer is always the same family that wrote the code. Implement
with Claude, review with GPT; implement with Codex, review with Claude; or
--backend codex,claudefans out to both and shows you where they disagree. - Plans are reviewable too —
rr plan.mdstress-tests a design doc before you build it. Most review tools only understand diffs. - Standards-aware —
--docsauto-discoversllms.txt,AGENTS.md, orCLAUDE.md(or takes explicit paths) and the reviewer flags deviations from your documented rules, not generic style opinions. - CI-gateable —
--json --fail-on highexits 2 when a high-severity finding lands. Pipe the envelope tojqor your bot of choice.
Install
pipx install rocket-review
Or the latest from source:
pipx install git+https://github.com/ledger-rocket/rocket-review.git
Requires Python 3.13+ and at least one backend:
- Codex CLI (default for plan reviews)
- Claude Code (default for code and diff reviews)
- opencode (
--backend opencode— any provider, including local models; experimental, see below) - or none of the above:
--backend api(or the--apishorthand) calls the OpenAI API directly — setOPENAI_API_KEYand install the SDK extra:pipx install 'rocket-review[api]'(orpipx inject rocket-review openaiinto an existing install)
--pr also needs the gh CLI.
Usage
rr plan.md # stress-test a plan before building
rr --diff # review uncommitted changes
rr --staged # review staged changes only
rr --commit abc1234 # review a commit
rr --pr 123 # review a GitHub PR (number, URL, or branch)
rr --pr 123 --repo acme/api-server # ...from outside that repo's checkout
git diff HEAD~3 | rr # pipe anything
rr src/auth.py --docs # review files against your documented standards
rr --diff --no-config # ignore the config files (hermetic run)
rr --version # print the installed version
Default backends by mode
With no --backend, the reviewer follows the review mode:
| Mode | Default | Why |
|---|---|---|
plan |
codex |
focused plan reviews, the fastest runs, schema-enforced JSON |
code |
claude |
deeper findings on source files, with fewer false alarms |
diff |
claude |
deeper findings on changes, with fewer false alarms |
These come from measuring the backends against each other on rocket-review's own eval corpus, not from a preference between vendors — and they are only defaults.
- Overriding — an explicit
--backendalways wins, in any mode, single (--backend codex) or as a list (--backend codex,claude).--modeis applied before the backend is chosen, sorr plan.md --mode codereviews as code and uses the code default. To change the table itself for a project or for yourself, set[backends]in a config file. - Missing backend — if the mode's default isn't available,
rruses the next available one and says so in a single line on stderr; pass--backendto choose explicitly and silence it. The order is the other agentic CLI first, thenopencode(skipped when--effortis set, which it doesn't support), thenapi— and only when bothOPENAI_API_KEYand the SDK extra are present, since a substitute that can't run would defeat the point. The fallback is never silent. If nothing is available,rrerrors with the install hint for the backend the mode wanted. - Two opinions at once —
--backend codex,clauderuns both and prints each review under its own heading. Some teams do this on pre-merge changes and read the disagreements; others keep the single per-mode default and spend the time elsewhere. Both are reasonable — it is a choice about your pipeline, not a recommendation.
Pick your reviewer
rr --diff --backend claude # Claude reviews (read-only sandbox)
rr --diff --backend opencode:ollama/qwen3 # fully local (opencode → Ollama)
rr --diff --backend codex,claude # both, side by side
rr --diff --backend codex:gpt-5.6-sol,claude:claude-opus-4-8
rr --diff --effort high # more reasoning effort (per-backend flag)
Model names: the codex backend passes no -m, so it honors your codex default
(set model in ~/.codex/config.toml). On ChatGPT plans use gpt-5.6-sol, the
ChatGPT-account-accessible 5.6 variant — codex signed into a ChatGPT account rejects
the bare gpt-5.6 alias and gpt-5.6-codex. The --backend api path (API-key auth)
defaults to gpt-5.6-terra — balanced cost/quality. Use --model gpt-5.6-sol for
max quality (flagship pricing) or gpt-5.6-luna for the cheapest tier. rr always
uses explicit, suffixed model names and never relies on the bare gpt-5.6 alias,
which points at the flagship today but can be remapped by OpenAI.
Use --effort to set reasoning effort; values differ per backend (codex/api accept
e.g. minimal|low|medium|high, claude low|medium|high|xhigh|max) and an unsupported
value fails loudly at the backend. opencode has no effort flag, so --effort errors
if opencode is among the selected backends. Heavy --effort high reviews — especially
on reasoning models — can outrun the default 900s (15 min) subprocess timeout; raise it
with --timeout 1800.
The Codex and Claude backends run agentically in read-only mode: they navigate your
project — imports, tests, related files — before judging. That context is what makes the
review worth reading. The api backend is the exception — it calls the OpenAI API
directly on the supplied content plus any files it references, without navigating your
project.
opencode is experimental. The integration works, but end-to-end review reliability depends on the provider you have configured, and non-interactive
opencode runcan restrict the read-onlyplanagent's tools.rrmaterializes the diff and feeds the prompt to opencode on stdin so it always reviews the real change, but for a gated CI check prefercodexorclaude. The local-model value prop stands — point opencode at Ollama to keep everything on your machine — just verify its output before trusting it as a gate.
Structured output
rr --diff --json | jq '.findings[] | {severity, title, backend}'
rr --staged --json --fail-on high && git commit # block the commit on high+ findings
Every finding carries severity, title, file, line, why, fix, backend, model.
The envelope leads with a summary block — findings_total, per-severity counts
(explicit zeros for absent severities), worst_severity, per-backend verdicts, and
the gate result when --fail-on is set — so an agent gets the counts and the
gate answer without parsing the findings array. A top-level schema_version
(currently the string "1") tags the envelope shape: it bumps only on a breaking
change — a field removed, a type or meaning changed — so new fields can appear
without a bump and consumers should ignore keys they don't know. Consumers should
match the value exactly against known versions (no numeric ordering is implied)
and treat anything else as unsupported. Backend output over 4000 chars is
truncated inline — raw keeps the head plus a marker naming the full length — so the
envelope stays bounded and no review text (which may quote proprietary code) is written
to disk; pass --full to inline the untruncated output instead. Parse failures and
backend errors fail the gate closed.
Review modes
plan— auto-detected for.md/.txt/.planfiles: completeness, ordering, risks, over-engineering.code— source files: correctness, security, performance, maintainability.diff— for--diff/--staged/--commit/--pr/stdin: bugs introduced, missing changes, contract breaks.
Override with --mode, add focus with --prompt "check the locking". The mode also
picks the reviewer — see Default backends by mode.
Project standards (--docs)
Point the reviewer at your project's standards docs — it flags deviations from your documented rules:
rr src/auth.py --docs # auto-discovers llms.txt / AGENTS.md / CLAUDE.md
rr src/auth.py --docs docs/standards.md docs/smells.md
Relative markdown links inside the docs are followed one level, so an index file
(like llms.txt) pulls in everything it references. Bare --docs errors if none of
llms.txt / AGENTS.md / CLAUDE.md exist in the current directory — pass explicit
paths when your standards live elsewhere. --llms [PATH] is kept as a compatibility
alias for --docs [PATH]: bare, it takes the project's llms.txt if the repository
carries one and errors if it cannot — like a bare --docs, and unlike a config file's
docs = true, which is a standing preference and stays silent; with a path, it reads
what you name.
Inside a git repository, an auto-discovered doc and every link followed out of any doc
must be a file the repository tracks — the repo, not you, chose those, and a standards
doc is copied into the prompt verbatim. Name a path directly (--docs CLAUDE.md) to
read one the repository does not carry. The full rule is under
What rr will read as a standards doc.
Config file
Two optional TOML files hold the defaults you would otherwise retype:
- Project —
.rocket-review.toml, found by walking up from the current directory to the git root (and no further; outside a repo only the current directory is read). - User —
~/.config/rocket-review/config.toml, or$XDG_CONFIG_HOME/rocket-review/config.toml.
Precedence: CLI flag > project file > user file > built-in default, settled per
key. A project file that sets only [backends] leaves your user file's timeout in
force, and the same holds inside [backends] and [models].
timeout = 1800 # --timeout
effort = "high" # --effort
fail_on = "high" # --fail-on (needs json = true, exactly as the flag needs --json)
json = false # --json
full = false # --full
docs = true # --docs with no path (auto-discovery); or a list of paths
[backends] # per-mode default backend, overriding the built-in table
plan = "codex"
code = "claude"
diff = "claude"
default = "claude" # optional: covers any mode you leave out above
[models] # per backend, i.e. what `--backend name:model` pins
codex = "gpt-5.6-sol"
claude = "claude-opus-5"
Every key mirrors a flag — a config file changes what rr does by default, never what
it can do. What to review (--diff, --pr, files, --mode, --prompt) stays on the
command line, where it is visible in the invocation.
Anything else is an error, named rather than ignored: an unknown key or mode, a backend
rr doesn't have, a non-integer timeout, a fail_on that isn't a severity, malformed
TOML (reported with the file and the parse position). Config is validated before any git,
gh, or backend work starts.
--no-config ignores both files. It is also the way out of a json = true,
full = true, or docs you don't want on this run: rr has no --no-json-style
inverse flags, so a lower layer can only be turned off by a higher config file
(json = false in the project file) or by --no-config. Flags always win where they
can say anything at all.
In a gated CI job, pass --no-config (or at least an explicit --backend). The
config a job picks up comes from the branch under test, which on a fork's PR is the
contributor's: [backends]/[models] decide which model reviews the code, so a
config change alone can downgrade the reviewer your gate depends on. --fail-on is the
one direction that is safe either way — a file can only tighten a gate the job did not
set, never loosen the one it did, since your flag outranks it.
docs in a config file
docs = true means "use this project's standards doc if it has one" — llms.txt /
AGENTS.md / CLAUDE.md, and a project without one is not an error the way a typed
--docs is. Which project depends on which file asked:
- Project file — looks in the directory holding the
.rocket-review.toml, so everyone gets the same standards wherever in the repo they run from. - User file — looks in the checkout you are in (its git root, or the current
directory outside a repo), never beside
~/.config/rocket-review/config.toml, which is nobody's project.
Explicit docs paths are relative to the config file that names them.
What rr will read as a standards doc
One rule decides every docs path, whether a config named it, discovery found it, or a markdown link inside another doc points at it:
A doc the repository chose is read only if the repository tracks it (at
HEAD), it resolves inside the directory it came from, and it is not inside.git. A doc you name is read as-is.
"The repository chose it" covers three cases:
- a path in a project's
.rocket-review.toml— that file is repository content, and on a fork's PR it is a contributor's; - anything auto-discovered (
docs = true, or--docs/--llmswith no path) — the repo decides which file answers the pattern, whoever asked for the pattern; - every markdown link followed out of a doc that resolves inside a repository — the
link was written by whoever wrote that doc. This holds even for a doc you named
yourself: naming
--docs STANDARDS.mdvouches for that file, not for what it points at.
Everything is decided on the resolved path, so a tracked symlink is judged by the file
it actually opens — the repository carries the link, not its target. Untracked and ignored
files are yours, not the project's: a .env, a key, a private note. A named path that is
refused stops the run and says so; a discovered or linked one is skipped with a warning,
since discovery is a standing "if there is one".
Outside a git repository nothing is tracked, so what remains is the directory
confinement and the .git guard: a config's docs must stay inside the config's own
directory, and a link inside the doc's.
What you name is yours — --docs PATH, --llms PATH, or a path in your own
~/.config/rocket-review/config.toml — and is read wherever it points, including files
no repository tracks. The single exception is .git, which rr never reads into a
review, as a footgun check.
--docs and --llms sit on the same boundary in both forms, which is what makes them
the alias this README calls them: bare, each takes what the repository offers; with a
path, each reads what you named.
How it works
rr assembles a review prompt — a mode-specific rubric, your standards docs, and
the plan or diff under review — and hands it to an agentic CLI (Codex, Claude Code,
or opencode) running read-only inside your project. Because the reviewer runs
in your checkout, it can open imports, tests, and related files to understand
context before it judges, rather than reasoning from the diff alone. It then
returns either prose or a parsed findings envelope (--json). There is no
rocket-review server in the loop: the only thing that leaves your machine is the
review request — the diff or plan, your standards docs, and any files the reviewer
opens — sent to whichever backend and provider you chose. Point rr at a local
opencode/Ollama model to keep everything on your machine.
Security & data flow
rr runs the reviewer in a read-only sandbox (no writes to your files), but
read-only is not the same as safe:
- Your code leaves your machine. Each review sends the diff/plan, your standards docs, and any files the reviewer opens to that backend's provider — codex/api → OpenAI, claude → Anthropic, opencode → whichever provider you configured (point it at a local Ollama model to keep everything on your machine).
- Read-only stops writes, not reads. An agent can still read any secret your shell
can (
.env,~/.aws, tokens) and send it upstream. - Untrusted input can prompt-inject the reviewer — a hostile PR body, diff, comment,
or
AGENTS.mdcan try to steer an agentic backend. Be especially careful with--pron a dev machine. - A repo's
.rocket-review.tomlconfigures the runs you make inside it — including which backend, and therefore which provider, gets your code, and which model at what cost. The docs it can name are bounded (seedocsin a config file) and it can only select backends you have installed, but it is repo-supplied input: read it like any other tooling config a clone brings with it, or use--no-config.
Don't run agentic backends against untrusted repos or PRs on a machine where readable secrets exist. See SECURITY.md for the full threat model and how to report a vulnerability.
Requirements
- Python ≥ 3.13
- OS — macOS or Linux
- A backend CLI, installed and authenticated — you only need the one(s) you use:
codex— Codex CLI, signed in with your ChatGPT/OpenAI accountclaude— Claude Code, on a Claude subscription or API key. Needs a version supporting--permission-mode manual(Claude Code 2.1.x+); older CLIs fail the review closed with a usage error. Check withclaude --help | grep -A3 permission-mode.opencode— opencode, configured for any provider (including a local Ollama model)api— no CLI, but needs the OpenAI SDK (pipx install 'rocket-review[api]', orpipx inject rocket-review openai); setOPENAI_API_KEYandrrcalls the OpenAI API directly
ghCLI, authenticated, for--pr
Agent integration
Drop into your CLAUDE.md / AGENTS.md:
Before pushing non-trivial changes, run `rr --diff --docs` and address the findings.
For plans, run `rr plan.md --docs` before implementing. Use a 900000ms timeout.
Notes
- Every backend runs in a read-only sandbox on your project — no writes: Codex
runs with
-s read-only, Claude Code with a read-only tool allowlist under--permission-mode manual, and opencode with its built-in read-onlyplanagent (edit/write denied at the tool level). Read-only stops writes; it does not stop the agent reading readable secrets and sending them to the backend's provider — see Security & data flow. --fail-onrequires--json— including when a config file is what set it; the error names the file.- Exit codes: 0 no gate tripped · 1 operational error (or every backend failed) · 2 findings at/above
--fail-on. A partial backend failure warns on stderr but still exits 0 — gate CI with--json --fail-onto fail closed.
Contributing
Issues and PRs are welcome. To set up a dev environment:
python3 -m venv .venv
.venv/bin/pip install -e ".[dev]"
.venv/bin/pytest -q # run the tests
.venv/bin/ruff check . # lint
.venv/bin/mypy rocket_review/ # type-check
.venv/bin/yamllint . # yaml lint
CI gates all four plus a package build — run them before opening a PR.
License
Apache-2.0 licensed. See LICENSE.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file rocket_review-0.3.1.tar.gz.
File metadata
- Download URL: rocket_review-0.3.1.tar.gz
- Upload date:
- Size: 95.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
29190a989e02d28e14af19df2937da917306c3b40e920d8cc00fffc1c50d58fa
|
|
| MD5 |
f765b5e8cdeb5df676946fe16221a161
|
|
| BLAKE2b-256 |
1d5c6166c05aca15cfdcf96a9045404b052615a7870f4c82498e2e4a532d9de9
|
Provenance
The following attestation bundles were made for rocket_review-0.3.1.tar.gz:
Publisher:
publish.yml on ledger-rocket/rocket-review
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rocket_review-0.3.1.tar.gz -
Subject digest:
29190a989e02d28e14af19df2937da917306c3b40e920d8cc00fffc1c50d58fa - Sigstore transparency entry: 2336171452
- Sigstore integration time:
-
Permalink:
ledger-rocket/rocket-review@4e040b7518156839555efad9e9a35fad0fbe3e2d -
Branch / Tag:
refs/tags/v0.3.1 - Owner: https://github.com/ledger-rocket
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@4e040b7518156839555efad9e9a35fad0fbe3e2d -
Trigger Event:
release
-
Statement type:
File details
Details for the file rocket_review-0.3.1-py3-none-any.whl.
File metadata
- Download URL: rocket_review-0.3.1-py3-none-any.whl
- Upload date:
- Size: 54.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
792d5f0a902b98dd7e3c6052ecf9450def59ab093bfafdc551cfb17cdff6a8d6
|
|
| MD5 |
3e758446e7c30a7763fc00f42741f13a
|
|
| BLAKE2b-256 |
1d46a1526405e6cfe4de071b6316c4c737097166571eba1586a0ba676d62a3ee
|
Provenance
The following attestation bundles were made for rocket_review-0.3.1-py3-none-any.whl:
Publisher:
publish.yml on ledger-rocket/rocket-review
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rocket_review-0.3.1-py3-none-any.whl -
Subject digest:
792d5f0a902b98dd7e3c6052ecf9450def59ab093bfafdc551cfb17cdff6a8d6 - Sigstore transparency entry: 2336171488
- Sigstore integration time:
-
Permalink:
ledger-rocket/rocket-review@4e040b7518156839555efad9e9a35fad0fbe3e2d -
Branch / Tag:
refs/tags/v0.3.1 - Owner: https://github.com/ledger-rocket
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@4e040b7518156839555efad9e9a35fad0fbe3e2d -
Trigger Event:
release
-
Statement type: