Skip to main content
obversa-review opens a git diff as a review page in a browser pane beside the terminal. You read the diff, attach comments to lines, and return them. The command writes the annotations to stdout as structured output, so a script or an agent reads them without a screen.

Use

The command reviews one of three diffs. Run it inside the repository, or point it at one with --cwd.
The three are one choice: naming two modes on one command line (--range main..HEAD --staged) is a usage error, whatever their order. A diff that holds an unresolved merge conflict is refused whole with a message naming the conflicted path. Git describes such a path as a combined diff, which a review could only show as a missing file, so the command never opens a page that looks clean over a conflicted tree. The page is placed through the absolute path a host injects as OBVERSA_SURFACE_BIN — the cmux host’s workspace launcher binds it to its obversa-surface glue for every terminal and agent in a workspace — and with the variable unset the default browser opens the page; nothing is looked up on PATH. The placement command receives a one-time launch URL (single use, valid for a minute) that redirects to the page; the page URL with its token never enters a process argument. Other flags:
  • --cwd <dir> runs against another repository directory.
  • --no-open does not place the pane. The command prints the page URL on stderr, and you open it yourself.
  • --app <name> overrides the surface app name. The name sets the frame markers on stdout (see below).
The working tree diff is git diff, so untracked files do not appear. The diff, the code around each hunk, and the file list are captured twice, and the review is accepted only when both captures agree on every line, every file read, and the file list. If the repository changes between them, the command tries the pair again — three attempts in all — and then stops with The repository changed while the review was being captured; retry when it is quiet. A change that is made and undone the same way during both captures is not detected; the command takes no lock on the repository. In cmux the page opens in a split beside the terminal. Outside cmux it opens in your default browser. How placement works.

What the page offers

  • The diff, with syntax highlighting by file type.
  • A file tree with two views. Changes lists the changed files with file-type icons, +/ line counts, and a status letter. All files lists every tracked file in the repository. Click a changed file to scroll to its diff.
  • Collapsed N unmodified lines bands between hunks. Click one to see the unchanged code around a hunk. Expand full file on a file header opens every band in that file. Full-file context is available in the working tree and staged modes, not in range mode.
  • Go to definition for JavaScript files (.js, .mjs, .cjs). Click an identifier to jump to the line that defines it, when that line is in the diff. When the definition is not shown, the tooltip names its line number.
  • A + button on every diff line. Click it to write a comment on that line. You can remove a comment before you return.
  • Return ends the review with the comments you wrote, or with none. Cancel ends it with no comments and a cancelled decision.

What comes back

The command writes one framed JSON object to stdout, so a caller captures it with a pipe. The one-line summary goes to stderr. For the default app name the markers are <<<REVIEW_RESULT_V1>>> and <<<END_REVIEW_RESULT_V1>>>. The marker name is the app name in upper case, with punctuation replaced by _. The payload is the surface result: the one result shape every Obversa review surface returns, whatever it shows. The surface field names the package that answered and the version that was resolved, on every status, so a consumer can always say which surface produced the frame.
An annotation may carry a thread: an ordered list of replies, each an author and a body, shaped and bounded like the annotation itself. A responder agent appends to it with appendToThread(annotation, entry) from @obversa/source — the call returns a new annotation and never mutates the input — and a thread submitted with the review rides the framed result intact, so the next round sees the conversation so far.
  • surfaceId names this review session. gateId names the pipeline gate that opened it, or null when you ran the command directly.
  • decision agrees with the annotations: changes-requested carries at least one, and approved carries none. A submission that says otherwise is refused and the page stays open, so a decision is never rewritten on the reviewer’s behalf. cancelled and timed-out are set only by the session’s own ending, never by a submission.
  • Each annotation has an anchor: target is the repository-relative file path as it appears in the diff, side is old for a deleted line or new for an added or unchanged line, and position is the line number on that side. An anchor is those three plain values and nothing else; one with an extra, hidden, or computed field is refused. body is the comment text with surrounding whitespace removed. author is the reviewer, and createdAt is when the comment was written.
The bounds are fixed. A review returns at most 500 annotations. Each body is cut at 4000 characters. An annotation must anchor to a line that appears in the diff; the server drops any other. status is completed when the reviewer returns. cancelled, timed_out, and interrupted carry a short detail and a payload with the same surfaceId and gateId, decision set to cancelled or timed-out, and no annotations, so a gate can still route the outcome. The exit code is 0 for completed, 1 for every other status, and 2 for a bad argument.

Security

The page runs on the Surfacer: one loopback server, one session token, one result. The static page names nothing under review: not the diff, not the ref, not even the highlight rules, which are built from the tokens the diff contains. The page fetches the diff, its label, and those rules from the session API with the bearer token, so a local process that finds the port sees a shell that is the same for every review. The address the page fetches from is a path on the session’s own origin or an absolute http or https URL; a path that would leave the origin once parsed is refused. Nothing in a diff line can execute. The page has no inline script, a strict Content Security Policy allows scripts and styles from the session origin only, and every line is rendered as text. The returned annotations are checked on the server, and one that does not anchor to a real diff line is dropped. Comment bodies are trimmed and cut at 4000 characters, and are otherwise returned as written: the secret redaction the runtime applies to other responses does not apply to them, because a comment can quote code.