ocra

CLI reference

Commands, options, output and exit codes.

ocra review

Reviews code changes in the current Git repository.

ocra review [options]
OptionMeaning
(none)Review uncommitted changes: staged, unstaged and untracked files
--from <ref>Review --to since it diverged from <ref> (merge base)
--to <ref>End of the range; defaults to HEAD; requires --from
--commit <sha>Review a single commit against its parent
--format text|json|sarifOutput format; default text. sarif writes SARIF 2.1.0
--output <file>Write the result to a file instead of stdout
--pr <number>Review a GitHub pull request; see GitHub pull requests
--repo <owner/name>Repository of --pr (default: GITHUB_REPOSITORY or origin)
--mr <iid>Review a GitLab merge request; see GitLab merge requests
--project <id|path>Project of --mr (default: CI_PROJECT_ID or origin)
--publishWith --pr or --mr: post the review to the pull or merge request
--fullWith --pr or --mr: review every file, not only what changed since the previous review
--max-cost-usd <n>Spend limit for the run. At 80% of it review tasks stop: no new one starts, and those still running are stopped, keeping what they found; their files count as not reviewed, so the run exits 3. Verification and judging use the rest. If that runs out too, the remaining findings are reported not verified, so they cannot block, and the report says so. Running tasks report their spend about every 10 seconds, so a run can pass the limit by what each one spends in that time plus its step in progress. Tasks run bundle by bundle, so a limited run finishes some files rather than starting all of them, and with --pr the next review continues with the files it left
--planShow selected and excluded files, bundles, review tasks and the size of each first prompt, including the one plan call a task makes under --ultra or for a bundle of five or more files, without calling a model (free). Grouping by the light model is skipped, so a change set large enough to be grouped is shown one file per bundle (by directory past 20 files). With --pr it plans a full review, not only what changed since the last one, and it writes no session log
--import-sarif <file>Add the results of a SARIF 2.1.0 log an analyzer wrote (Semgrep, CodeQL and others) to the review; repeatable. Only results on lines the change touches are kept, and they are verified and judged like a reviewer's findings. See SARIF input. Not with --plan
--ultraFavor recall over cost; see Modes
--reviewers <ids>Run only these reviewers, comma-separated (for example security)
--temperature <n>Sampling temperature for every model call, 0 to 2; overrides sampling.temperature in Configuration. Unset, each provider's default applies
--seed <n>Sampling seed for every model call, where the runtime and provider support one; overrides sampling.seed. The opencode runtime has no seed setting and reports it as not applied
--config <file>Read this configuration file instead of the repository's .ocra/config.json (with --pr or --mr, instead of the base branch's). The file is yours, so it applies with --no-repo-config too; plugins it names are resolved from its own directory, and are still not loaded for pull and merge requests. See Configuration
--no-repo-configIgnore .ocra/config.json and the plugins it lists (with --pr, also the base branch's); models come from OCRA_MODEL_* or from --config; memory, rules and AGENTS.md come from the --from commit or are not used. Use it on code you do not trust
-h, --helpShow help

--commit cannot be combined with --from or --to.

Progress

Progress goes to stderr, so --format json output on stdout stays machine-readable:

[ocra] Reviewing: Working tree changes
[ocra] 4 file(s) selected, 1 excluded · risk tier: lite
[ocra] 2 bundle(s) (grouped)
[ocra] 2 review task(s)
[ocra] correctness-1 started: auth session handling (2 file(s))
[ocra] correctness-1 reviewing with google/gemini-3.5-flash
[ocra] correctness-1 google/gemini-3.5-flash: 3 step(s), 3 tool call(s) (read_file 2, task_done 1), 19558 in / 118 out / 0 reasoning tokens, $0.0062
[ocra] correctness-1 completed in 5.9s · 0 finding(s)

When a model is quiet for 30 seconds, ocra prints Model is thinking... so a long review never looks stuck.

Exit codes

CodeMeaning
0Review finished; verdict approved, approved_with_comments or minor_issues
1Verdict significant_concerns: at least one critical finding the verifier confirmed. Unconfirmed critical findings give minor_issues and exit 0. The verdict is advice and can be influenced by the change under review; see How it works. With --pr, a maintainer's /ocra override for the head commit turns this into 0 (GitHub)
2Usage, Git or configuration error, or no review task completed. The error line names its code, as in ocra [VCS_REF_UNKNOWN]: Unknown commit: nope (codes)
3Review incomplete: a selected file was not reviewed (its task failed, timed out, hit the spend limit or the task limit, or no reviewer covers it), or a critical finding could not be verified because verification failed or ran out of budget. This wins over 1: an incomplete review never reports as merely blocking, so CI cannot let it pass
130Interrupted with Ctrl-C or SIGTERM; the partial report is still printed and nothing is published

JSON output

--format json prints the full report: the change request, risk tier, verdict and summary, coverage for every changed file (reviewed, failed, unreviewed when no task for it started, unchanged since the previous review, or excluded with a reason), for pull requests whether the run was incremental or full and why (scope), bundles, task outcomes, reviewer/bundle pairs the matrix skipped, findings verification dropped (refuted, with the reason), how many critical findings verification failed to check (unverifiedCriticals), what the judge merged, dropped or recalibrated (judgement), findings, the comparison with the previous review (rereview: fixed, not reproduced, not re-checked, unchanged and dismissed earlier findings), findings the repository's memory accepted (remembered), a maintainer's override in changeRequest.override, token usage and cost, with a spend limit the limit and whether the run reached it (spendLimit: reached is review when review tasks stopped, total when the whole limit ran out), and warnings. A session's report.json has the same shape.

The format is versioned ("version": 1) and is a contract: later releases only add optional fields, or change the version. Each finding has fingerprint (stable across runs; ocra memory add takes a prefix), reviewer, category, severity, verification (confirmed, uncertain or unchecked), file, lines ({ start, end }, absent when the finding is not tied to lines), inDiff, status (new, or unfixed when an earlier review reported it), title, body, optional suggestion, evidence, code (the quoted code), under --ultra lowConfidence, and provenance: the task in tasks that reported it and, when the runtime names it, the model. Each task in tasks carries its usage (tokens and cost; a finding's cost is its task's). The report's provenance says what the review was made with: ocraVersion, promptHash (a hash of the system prompts this configuration sends: each enabled reviewer's and each helper stage's, never the change), configHash (a hash of the effective configuration and the flags that change the review, --reviewers, --ultra and --max-cost-usd; sampling and secrets are left out, and keys are only ever named, never stored) and sampling: the temperature and seed the runtime applied, and in notApplied those it was asked for and could not pass on; and agents: for each enabled reviewer and the verifier, judge and helper roles, its model tier, its effort when one is set, whether it was applied (absent when the agent made no call), and in notApplied the sampling settings its calls left out. A JSON Schema of the report (draft 2020-12) is generated from the code and tested against it; reportJsonSchema() in @open-cr-agent/core returns the same schema. The JSON of --plan is versioned the same way ("version": 1): the change request, risk tier, selected and excluded files, bundles, tasks with their estimated prompt size and their effort when one is set, skipped reviewer/bundle pairs, whether model grouping was skipped (groupingSkipped), the total estimated first-prompt size (promptTokens) and warnings.

SARIF output

--format sarif writes the review as a SARIF 2.1.0 log, the format code scanning and security dashboards read. To upload it to GitHub, see Code scanning.

  • Rules and results. There is one rule per reviewer category (correctness, security and so on) and one result per finding:
    • its level comes from the severity: critical is error, warning is warning, suggestion is note;
    • its message is the title, body and suggestion;
    • its region is the finding's lines;
    • partialFingerprints holds its fingerprint (ocra/v1);
    • the reviewer, severity, verification, status, task and, when known, model are properties.
  • Findings without lines. A finding ocra could not tie to lines applies to the whole file: it has no region.
  • Carried-over findings. In a pull request, findings an earlier review reported that are still open, in files this run did not review again, are results too. They sit under the rule ocra-carried-over, marked unchanged and without lines: the review state keeps no lines for them.
  • Incomplete reviews. A review that exits 3 is marked unsuccessful. The number of files not reviewed and the warnings are notifications. The verdict, risk tier, files not reviewed and cost are run properties.

Text from the change and from models stays text. Brackets and backslashes are escaped, so no embedded link can form, braces are doubled, as SARIF requires, and web addresses get a zero-width space, as in pull request comments. --plan has no findings, so it cannot write SARIF.

SARIF input

ocra runs no analyzer. Run Semgrep, CodeQL or another tool yourself and pass its SARIF 2.1.0 log with --import-sarif <file> (repeatable; at most 20 MB each). Each run in the log becomes a task of its own in the report, sarif-<tool>-<n>, with the tool's name as the reviewer, no cost, and the files it reported on; its findings name it in provenance.task.

  • Only results on the change are kept: on a reviewed file, on lines inside a hunk of the diff. The rest of a whole-repository scan is left out and counted in a warning, as are results without a file and lines, on paths outside the repository, or without a message. At most 200 results per run are taken.
  • Severity comes from the result's level, else the rule's default: error is critical, warning is warning, note and none are suggestion, the inverse of SARIF output. The title is the rule's short description, name or id; the body is the message, the rule's description and a line naming the tool, its version, the rule and its help link.
  • The quote decides the lines. The result's snippet, or the file's lines, is anchored like a reviewer's quote; a quote that no longer matches makes the finding file-level.
  • From there on it is a finding like any other: memory and dismissals apply, Verify checks it against the diff, the judge merges it with the reviewers' findings (a tool's duplicate of a reviewer's finding is merged there), and it counts toward the verdict.

Tested with the log Semgrep 1.178.0 writes; other tools' logs follow the same standard.

Sessions

Every review (not --plan) writes .ocra/sessions/<id>/events.jsonl (one event per line, appended as the run progresses) and report.json. The <id> is the run id: the first progress line names it, the JSON report carries it as runId, the summary comment shows it under Coverage and cost, and the SARIF log carries it as automationDetails.id (ocra/<id>), so one run can be found again from any of them. The directory carries its own .gitignore, so logs are never committed and never show up as changes in the next review.

ocra metrics

Counts over the finished reviews in .ocra/sessions/, read from each session's report.json; nothing calls a model:

ocra metrics                       # a text table
ocra metrics --format json         # for a dashboard or a script; carries "version": 1
ocra metrics --since 2026-10-01    # runs started at or after a date
ocra metrics --sessions /srv/ocra  # another sessions directory, as on a CI runner

It reports runs (by verdict, how many left files unreviewed, how many sessions had no readable report), cost (total and per run, tokens), findings (reported, unique by fingerprint, by severity and verification), what became of findings reported earlier (fixed: the code is gone in a later review; dismissed: a reviewer dismissed them; their ratio as the acceptance rate), and the same per reviewer with its tasks, failed tasks and cost. A finding's reviewer is the one that first reported its fingerprint. Only reports of version 1 are counted; the JSON output is a contract like the report, versioned the same way.

ocra --version

Prints the version.

ocra memory

Some findings are correct but accepted: the risk is handled elsewhere, or the team decided to live with it. Remember them so ocra stops reporting them:

ocra review                                  # every finding shows an id, such as #1a2b3c4d
ocra memory add 1a2b3c4d --reason "Retries are capped by the gateway."
ocra memory list

ocra memory add looks the id up in the newest session under .ocra/sessions/; pass --session <dir> to use another one. Entries go to .ocra/memory.json; commit the file to share them. A remembered finding (same reviewer, file and quoted code) is left out of the report and counted instead, and reviewers see the accepted findings for the files they review so they do not raise them again. For pull requests the file is read from the base commit, so a pull request cannot silence its own findings. An invalid .ocra/memory.json stops the review with an error, like an invalid .ocra/rules.json.

ocra login, ocra logout, ocra whoami

In development. ocra Cloud (ADR-0024) lives at https://app.ocracloud.com; OCRA_CLOUD_URL names another server. Nothing else in ocra uses these commands: reviews work exactly as before without a login, and with OCRA_CLOUD=off the commands refuse to run.

ocra login                                    # shows a code and opens the browser to confirm it
ocra whoami                                   # the signed-in GitHub account
ocra logout                                   # ends the session on the server and deletes it here

ocra login uses the OAuth device flow (RFC 8628): it prints a code such as BCDF-GHJK, opens the server's page to confirm it (--no-browser only prints the link), and waits while you approve it in a browser signed in with GitHub. Approve only a code you just asked for yourself. The terminal then holds a session that can call models with the keys stored on the server and upload review results, but cannot read those keys or change the account; it shows on the server's sessions page and can be revoked there.

The session is saved in ~/.config/ocra/credentials.json ($XDG_CONFIG_HOME/ocra/, or %APPDATA%\ocra\ on Windows), readable only by you. Its access token lasts an hour and is renewed with a refresh token that changes on every use. While signed in, a model named ocra-<provider>/<model> goes through ocra Cloud with the key stored there for <provider> (an OpenAI-compatible chat endpoint the server lists), for example:

{ "runtime": "direct", "models": { "standard": "ocra-openrouter/qwen/qwen3.8-27b:free" } }

Such models are unpriced: the reported cost and --max-cost-usd do not count them, and ocra warns so. The prompts and the code they quote then pass through ocra Cloud on their way to the provider; it records the model, status, token counts and duration of each call, never the bodies. A review that names no ocra- model reads no session and never contacts ocra Cloud, and a provider the configuration declares under an ocra- name keeps its own declaration.

When signed in and the repository configures no models (no models in the configuration and no OCRA_MODEL_*), ocra review uses the default models you chose on the server's Default models page, and says so.

After each review, a signed-in CLI sends ocra Cloud the review's counts: the verdict, whether every file was reviewed, how many findings of each severity, files and tasks, tokens, cost and time, the source (local, GitHub, GitLab) and the ocra version. It never sends a path, a title, a finding's text or code. The repository is sent as a SHA-256 of its origin URL (credentials removed) salted with a random value kept in upload-salt next to the credentials, so the server can group reviews of one repository without learning which it is; the same repository on another machine gets another hash. --no-upload skips this for one review, OCRA_CLOUD=off for all, and a failed upload is only a warning.

Edit on GitHub

On this page