Review Panel
The Review panel loads a GitHub pull request into a local git worktree, runs an agent review pass against the diff, and lets the user push the resulting inline comments back to the PR — either one at a time or all at once.
Enabling
The panel is gated behind review.enabled (default false). Set it in
~/.loop/config.json (or a project override) to opt in:
"review": {
"enabled": true
}When disabled, the FE hides the Review tab from the panel picker and the
backend returns 403 for /review/* requests. The flag is layered
per-global / per-project / per-worktree the same way as github.gh_user.
Lifecycle
- Load — the FE fetches open PRs (
GET /review/prs→gh pr list) and renders them as a clickable list; the user picks one. The backend:- Looks the PR up via
gh pr view <number>for metadata and base/head refs. - Resolves the head SHA via
gh pr view --json headRefOid. - Creates a worktree off the PR head branch under the channel’s
dir_path. - Fetches the base ref into the parent repo (
git fetch origin <base>) soorigin/<base>resolves locally for the Run step.
- Looks the PR up via
- Run — the user clicks Run. The backend launches a single agent
run inside the worktree using the configured review prompt (
review.promptorreview.prompt_pathin~/.loop/config.json; falls back to a built-in default). The prompt tells the agent to compute the diff itself withgit diff origin/<base>...HEADfrom the worktree — keeps the containerargvsmall and lets the agent read individual files for context. The agent emits<review-comment path="..." line="N" side="RIGHT|LEFT">...body...</review-comment>blocks, which are parsed and streamed to the FE as they arrive. - Push — each comment ships with Push (single) and a Push all
(N) affordance in the header (when at least one comment is unpushed).
The backend uses
gh api ... /pulls/N/commentsagainst the captured head SHA so comments anchor to the right commit even if the PR is force-pushed later. - Close — closing the session deletes the in-memory session record and removes the worktree on disk. Pushed comments remain on GitHub.
Concurrency
A second POST /review/run while the first is still in flight returns
202 {"status":"in_progress"} without restarting the agent. Comments
keep streaming through the existing run.
Status transitions
Status is broadcast over the WebSocket as review.status events so
multiple panes / browser tabs stay in sync without polling.
idle ──load──▶ loading ──ok──▶ ready ──run──▶ reviewing ──ok──▶ ready
│ │
└──err──▶ error └──err──▶ errorConfiguration
{
"review": {
"prompt": "Review the diff between <diff> tags and emit one <review-comment> block per actionable issue. Skip style nits."
}
}Or, to keep the prompt out of the JSON file:
{ "review": { "prompt_path": "review.md" } }The latter is read from ~/.loop/review/review.md. Setting both is an
error; setting neither uses the daemon’s built-in default prompt.
Required output format
The parser anchors comments by the tag attributes, so an override prompt must instruct the agent to emit blocks shaped exactly like:
<review-comment path="path/to/file" line="N" side="RIGHT">
One paragraph describing the issue.
</review-comment>pathis repo-relative.lineis the line on the indicated side of the diff.sideis"RIGHT"for added/modified lines (the common case) or"LEFT"for lines removed from the base. This matches GitHub’spulls/{N}/commentsAPI, so the value is forwarded as-is on push.
Blocks that fail to parse are silently dropped, and the parser deduplicates by content hash so re-emitting the same block during streaming is safe.
See also
- api.md — full HTTP and event surface.
- layouts.md — how to add a Review pane.