prr
AI-powered pull request reviewer in your terminal. Review diffs, navigate findings, comment, and submit — without leaving the CLI.
Built with Go and Bubble Tea. Diffs styled by delta.

Prerequisites
Install
Download a binary from the latest release.
Or via Go:
go install github.com/andreujuanc/prr/cmd/prr@latest
Or build from source:
git clone https://github.com/andreujuanc/prr.git
cd prr
go build -o prr ./cmd/prr
Setup
On first run, prr creates ~/.config/prr/config.json:
{
"provider": "gemini",
"api_key": "YOUR_API_KEY",
"model": "gemini-2.5-pro",
"parallel_reviews": 3,
"pipes": []
}
Set your API key and you're ready to go. The pipes array is optional — see Pipe Targets for configuration.
Claude Code (zero-config)
If you already have the claude CLI installed and signed in, prr will detect it and offer Claude Opus / Sonnet / Haiku in the model picker without any API key in config.json. Auth is handled by Claude Code itself (subscription, OAuth, or ANTHROPIC_API_KEY), which is useful in environments where you can't easily set an Anthropic API key (e.g. Claude Enterprise).
The provider runs claude -p per request with a curated read-only toolset (Read, Grep, Glob, plus specific read-only git subcommands) and --permission-mode bypassPermissions, so it never edits files and never prompts for permission.
Usage
prr 42 # Review PR #42
prr # Pick from open PRs
prr --debug # Enable debug logging
Features
Three-pane layout — file tree, diff viewer, and AI panel side by side.
AI-powered review — Multi-pass batch review with structured findings. The AI reads diffs, uses tools (grep, git blame, read file), and produces a prioritized list of issues with severity ratings and a recommended verdict.
Inline findings — AI review findings render as severity-colored boxes directly in the diff view, positioned below their target lines. Toggle with F. Findings are sorted by severity (critical first) and support file-level, right-side, and left-side placement.
GitHub Actions status — The file tree shows an "Actions" entry with a live status dot (green/red/yellow). Select it to see workflow runs, jobs, and steps in the diff pane. Active runs poll every 15 seconds automatically.
Publish findings — Post individual findings as GitHub line comments (c), submit all findings as a batch GitHub Review (C), or post as a general PR comment (g). Confirmation modals prevent accidental submissions.
Pipe to external tools — Send findings to external processes (linters, ticket systems, Slack bots) configured in your config file. Pipe targets receive structured JSON, markdown, or plain text via stdin.
Review workflow — Track file review status, navigate between unreviewed files, submit your review to GitHub with approve/comment/request-changes verdicts.
Inline comments — Position cursor on any diff line and press c to leave a review comment directly on the PR.
Chat — Ask follow-up questions about any file or the PR as a whole. The AI has access to the full codebase via tools.
Model switching — Press m to switch between models on the fly. Choice is persisted to config.
Smart caching — Per-file review findings are cached. Re-running a review only re-analyzes changed files. Stale reviews are detected and flagged.
Refresh from origin — Press o to pull the latest changes without restarting.
Keybindings
Global
| Key |
Action |
Tab / Shift+Tab |
Cycle panes |
Ctrl+A |
Toggle AI panel |
Ctrl+B |
Toggle file panel |
a |
AI review (file or full PR) |
A |
Force re-review (ignore cache) |
m |
Switch model |
? |
Help |
q |
Quit |
File List
| Key |
Action |
j / k |
Navigate files |
Enter |
Select file |
l / h |
Expand / collapse directory |
Space |
Toggle reviewed status |
r |
Hide/show reviewed files |
n / p |
Next / previous unreviewed |
o |
Refresh PR from origin |
Diff
| Key |
Action |
j / k |
Move cursor |
G / g |
Jump to bottom / top |
Ctrl+D / Ctrl+U |
Half-page down / up |
+ / - |
More / less context lines |
Space |
Toggle reviewed status |
n / p |
Next / previous unreviewed |
c |
Comment on line |
F |
Toggle inline findings |
Review Panel
| Key |
Action |
j / k |
Navigate findings |
Enter |
Jump to finding in diff |
c |
Post finding as line comment |
C |
Submit all findings as GitHub Review |
g |
Post finding as PR comment |
| |
Pipe finding to first configured target |
x |
Open action menu (all publish/pipe options) |
Tab |
Switch to Chat tab |
Ctrl+S |
Submit review to GitHub |
Chat Panel
| Key |
Action |
Enter |
Send message |
Ctrl+K |
Clear chat history |
Tab |
Switch to Review tab |
Custom Instructions
Add project-specific review instructions that get included in every AI review:
.prr/instructions.md # preferred
.github/prr-instructions.md # alternative
Example:
- This project uses the repository pattern; data access should go through repository interfaces
- Error handling must use wrapped errors with fmt.Errorf("context: %w", err)
- All public functions need doc comments
Model Configuration
Model parameters are in ~/.config/prr/models.json:
{
"gemini-2.5-flash": {
"max_output_tokens": 65536,
"temperature": 0.2,
"thinking_budget": 8192
}
}
Available models (switchable with m):
| Model |
Thinking |
gemini-3.1-pro-preview |
Yes (16K budget) |
gemini-3.1-flash-lite |
Yes (8K budget) |
gemini-2.5-flash |
Yes (8K budget) |
Pipe Targets
Configure external processes to receive findings via stdin. Add a pipes array to ~/.config/prr/config.json:
{
"provider": "gemini",
"api_key": "...",
"model": "gemini-2.5-pro",
"pipes": [
{
"name": "Create JIRA ticket",
"command": "jira-create",
"args": ["--project", "BACKEND"],
"format": "json"
},
{
"name": "Notify Slack",
"command": "slack-notify",
"args": ["--channel", "#code-review"],
"format": "markdown"
}
]
}
Supported formats:
json — structured payload with file, line, severity, category, title, detail, suggestion, repo_root
markdown — formatted heading, metadata, and detail sections
text — compact plain text summary
Pipe targets appear in the action menu (x) and can be triggered directly with number keys (1, 2, ...) when the menu is open.
Files Excluded from AI Review
prr automatically skips files that aren't useful for code review:
- Binary files — images, fonts, archives, compiled artifacts
- Generated files — lock files,
.min.js, .pb.go, .gen.go
- Large diffs — files with diffs exceeding 100KB
- Vendored code —
vendor/, node_modules/
Excluded files still appear in the file tree (dimmed with a tag) but aren't sent to the AI.
State
Review state is stored per-PR at .git/pr-tui/<pr_number>.json. This includes review status per file, cached AI findings, and chat history. State is local to your clone and not committed.
Troubleshooting
If prr misbehaves — hangs during a phase, returns empty findings, or
crashes — the steps below collect the data needed to diagnose it.
Debug log
Every run writes to ~/.cache/prr/debug.log. It captures cache misses,
batch retries, parse failures with response prefixes, AOI routing, and
any warnings emitted by the pipeline. The file is append-only across
runs, so timestamps matter.
# Last 200 lines of the most recent activity
tail -200 ~/.cache/prr/debug.log
For verbose output, pass --debug on the command line. Extra detail
from the agent loop (per-round LLM calls, tool calls, tool results)
goes to the same file.
Capturing a hang without killing the process
If prr appears stuck in a phase, send SIGUSR1 to dump every
goroutine's stack to a file. The process keeps running and the file is
readable while it's open. You can dump as many times as you like —
each dump gets its own timestamp.
# In another terminal:
kill -USR1 $(pgrep -f 'prr')
# Find the latest dump:
ls -ltr ~/.cache/prr/goroutines-*.txt | tail -1
The dump shows which goroutine is doing what. The ones to look at:
- A goroutine in
parseSSEStream → http2pipe.Read is actively
receiving from the model.
- A goroutine in
http2ClientConn.roundTrip with no body read going
means we sent the request and never got a response — likely an
upstream silent hang. The provider's ResponseHeaderTimeout (90s
default) should turn this into a fast retry.
- Multiple goroutines parked in
sync.WaitGroup.Wait are the
orchestration layer waiting on those workers above.
Parse failures
When the AI's response can't be parsed as JSON, the log records the
first 500 chars of the response so you can see what the model
actually said. Look for lines like:
Warning: failed to parse batch JSON: invalid character 'm' looking for beginning of value — response prefix: "my analysis ..."
This tells you whether the model went conversational, hit a refusal,
returned markdown without code fences, or something else — the fix
depends on which.
What to share when reporting an issue
~/.cache/prr/debug.log (or just the last few hundred lines around
the failure).
- The most recent
~/.cache/prr/goroutines-*.txt if the run hung.
- The exact command you ran (
prr 42 --no-cache, etc.).
- Your model selection (
gemini-2.5-pro, etc.) — visible in the
first lines of debug.log.
That set is enough to reconstruct what happened without needing your
repo content.
License
MIT