CLI, hook and MCP
One policy, two points of control. The bot checks the pull request and stays an independent gate; the same team rules and the same judge are available to your agent right in the editor, before the code leaves for review.
First, pick your case
Everything after this is identical — the installation, the commands, the hook, MCP. Exactly one thing differs: where model access comes from. Pick the row that describes you and set up only that.
| Your case | What you set up |
|---|---|
| Just me, my own key a freelancer, a side project, your own provider account | LLM_API_KEY in the environment or in the settings file — the «Your own key» section below. No server is needed at all |
| A team, the company holds the key the CTO does not hand the key to developers | the review server address and your personal GitLab token — the «Through the team server» section. The instructions for whoever grants access: review server for the team |
| We have a corporate model gateway LiteLLM or your own proxy with personal tokens | the same «Your own key», but LLM_BASE_URL points at the gateway and LLM_API_KEY is your token to it, not the vendor key |
| The model is local Ollama or vLLM on your machine or on a shared server | LLM_PROVIDER and LLM_BASE_URL; no key needed. The details are in models and your own key |
Installation
A single file with no dependencies: neither Node nor Docker is required. You do need git in PATH — the binary does not bundle it.
| Platform | File |
|---|---|
| macOS · Apple Silicon | reviewgate-latest-darwin-arm64.xz |
| macOS · Intel | reviewgate-latest-darwin-x64.xz |
| Linux · x86-64 | reviewgate-latest-linux-x64.xz |
| Linux · ARM64 | reviewgate-latest-linux-arm64.xz |
| Linux · musl (Alpine) | reviewgate-latest-linux-x64-musl.xz |
| Windows · x86-64 | reviewgate-latest-win-x64.exe.xz |
The checksums are in SHA256SUMS next to the artefacts. Do check them: you are about to run an executable downloaded from the network.
The same files are published as a GitHub release — byte for byte, with the same checksums. Take whichever is faster for you; if this site is unreachable, the release still is.
curl -fsSLO https://reviewgate.dev/dl/reviewgate-latest-darwin-arm64.xz
curl -fsSLO https://reviewgate.dev/dl/SHA256SUMS
shasum -a 256 -c SHA256SUMS --ignore-missing
# no xz? brew install xz
xz -d reviewgate-latest-darwin-arm64.xz
chmod +x reviewgate-latest-darwin-arm64
sudo mv reviewgate-latest-darwin-arm64 /usr/local/bin/reviewgatecurl -fsSLO https://reviewgate.dev/dl/reviewgate-latest-linux-x64.xz
curl -fsSLO https://reviewgate.dev/dl/SHA256SUMS
sha256sum -c SHA256SUMS --ignore-missing
xz -d reviewgate-latest-linux-x64.xz
chmod +x reviewgate-latest-linux-x64
sudo mv reviewgate-latest-linux-x64 /usr/local/bin/reviewgate# PowerShell. Windows cannot unpack .xz out of the box — take 7-Zip (winget install 7zip.7zip)
curl.exe -fsSLO https://reviewgate.dev/dl/reviewgate-latest-win-x64.exe.xz
curl.exe -fsSLO https://reviewgate.dev/dl/SHA256SUMS
# the hash must appear in SHA256SUMS — an empty output means it does NOT match
$hash = (Get-FileHash reviewgate-latest-win-x64.exe.xz -Algorithm SHA256).Hash.ToLower()
Select-String $hash SHA256SUMS
& "$env:ProgramFiles\7-Zip\7z.exe" x reviewgate-latest-win-x64.exe.xz
Rename-Item reviewgate-latest-win-x64.exe reviewgate.exe
# move reviewgate.exe to a directory on PATH, e.g. %LOCALAPPDATA%\Programslinux-x64. For Alpine and other musl systems there is a separate linux-x64-musl build; it needs the libstdc++ package (the node:*-alpine images already have it). You cannot mix the builds up — on the wrong system the binary simply will not start.Your own key
The provider key is taken from the process environment or from your settings file in your home directory — never from the working copy. That is a security boundary: a clone of somebody else's repository must not steer your diff to somebody else's endpoint.
export LLM_API_KEY=... # your provider key
export LLM_JUDGES=... # optional: a default judge (without one, a single pass) To avoid setting variables in every terminal session, put them in a file. Declaring generators / judges there sets your own schema for local runs — it replaces the team one entirely (taken as a whole, generators required), the report says so, and --team-llm runs with the team roles instead. Details: the home config.
llm:
api_key: sk-...
generators: # your schema for local runs: taken as a whole,
- model: claude-sonnet-5 # it replaces the team llm.* from the repo config
judges: # (--team-llm runs with the team schema instead)
- model: claude-opus-5
# base_url: https://llm.вашакомпания.ru/v1 # корпоративный шлюз, если он есть The secret need not be stored in the clear: instead of api_key give api_key_command — a command from your password manager. Its output is stored nowhere.
llm:
api_key_command: op read op://dev/anthropic/credentialignore, the preset and the blocking threshold live only in the .reviewgate/config.yml of your repository. The home file sets where to go, not what counts as a problem: otherwise every developer would have their own policy, and a local run would stop predicting the bot's verdict on the pull request.Through the team server
If the company holds the key and has no intention of handing it out, your own ReviewGate server performs the review. All you need is its address and your personal GitLab token — the same one you use for GitLab. No model key required.
server:
url: https://bot.вашакомпания.ru
gitlab_token: glpat-... # your personal GitLab token
# gitlab_token_command: pass show work/gitlab # or this way, without storing it in the file The diff is assembled locally; the changed files go to the server, and it asks for the rest as it needs them — exactly what the judge actually required. The --local flag brings the run back to your own key, which helps outside the company network. How to enable the mode on the server is on a separate page for whoever grants access.
The first run
reviewgate doctor # check the environment before the first run
reviewgate rules # which team standards were read
reviewgate review # review the uncommitted changesdoctor checks the environment and says what is missing before you spend time and tokens: it names the source of every value and, in server mode, verifies access live. rules shows which rules were read from .reviewgate/config.yml — the same file the bot works from.
From there the binary explains itself: reviewgate help is an overview of every command and flag, reviewgate help review covers one in detail. The same material with examples and links is in the CLI reference.
Machine mode
With --json only the report goes to stdout; everything else goes to stderr. The exit codes are deterministic: 0 — the gate passed or is off, 2 — there are findings at or above the threshold, 1 — a failure (the error object goes to stdout with --json, otherwise the text goes to stderr). The default threshold comes from the team policy, and out of the box that is severity_gate: off — meaning without --fail-on the code is always 0.
reviewgate review --json --fail-on majorThe report fields, the error codes and the CI recipes are in the CLI reference.
The hook: a review before push
The hook intercepts the agent's git push and refuses to send changes that carry blocking findings. A measurement showed the gate belongs on the push rather than on every answer from the agent: the thing to economise on is how often it is called, not how deep the check goes.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [{ "type": "command", "command": "reviewgate review --hook-stdin" }]
}
]
}
} The blocking threshold is the team's own: the hook reads severity_gate from the repository config, so the decision before a push and the bot's verdict on the pull request agree. The only exception is a gate switched off by the policy (severity_gate: off, which is the default): the hook then applies its own major threshold and says so, since otherwise it would never block and would be useless. A threshold for a single run is set with --fail-on. The answer dialects are in the reference.
If you hang the hook not on the push but on the agent finishing its work (the Stop event), there will be no loop: the runner marks such a repeated event with a «continuing because you blocked me» flag, and on that flag ReviewGate skips the run instead of reviewing again. Otherwise a finding the agent cannot or will not fix would spin «stop → review → block → stop» without a ceiling, burning a full run per turn.
config.yml. With an ensemble of two generators and an arbiter that means minutes: our measurement on a change in two files came to 6.5 minutes and about $0.6, whereas --fast (one generator, no judge) took a minute and a half and cost four times less. That is by design: a false block on a push costs more than the wait, and on the pull request the same code is checked by the bot. If you need a faster gate, add --fast to the hook command, knowing the price: the findings are not confirmed by an independent model, and there will be more noise. A cheaper road that keeps the judge: declare your own LLM schema in the home config — the hook, like every local run, uses it, and the bot still reviews the merge request with the team schema.MCP: the agent calls the review itself
Two tools: review_changes checks the changes, and get_team_rules reports the team standards before the code is written rather than from a review afterwards. If the project has a config.yml that could not be parsed, get_team_rules says so plainly: «there are no rules» and «the rules did not apply» are different answers, and an agent must tell them apart.
{
"mcpServers": {
"reviewgate": { "command": "reviewgate", "args": ["mcp"] }
}
}run reviewgate help agent-setup and do what it saysThe command prints instructions written for an agent, and it translates them into the format of its own environment. We deliberately do not edit client configurations: there are many clients, one person often has several at once, and a file we guessed at would be worse than no file. The section is still worth reading, to understand what happens and why a setup sometimes «fails silently».
Connecting the server is not enough: the agent must know the tools exist
Many clients — Claude Code among them — load MCP tool descriptions lazily: at the start of a session the agent sees the server name but not its tools, and simply never thinks to call them. A tool that is not in the context will not be called, however precise its description. The cure is two lines in the project instruction file (CLAUDE.md, .cursorrules — whichever your agent uses):
The standards of this project come from the `mcp__reviewgate__get_team_rules` tool —
call it BEFORE writing code, instead of reading the ADRs by hand.
Before finishing a task and before `git push`, check the changes with the
`mcp__reviewgate__review_changes` tool — it is the same judge and the same rules
that check the pull request. The difference is measurable. On a project with eight ADRs we asked an agent to work out the team standards before writing code, without mentioning ReviewGate: without these lines it spent about three minutes reading documents and code by hand; with them it called get_team_rules at the sixteenth second and got the same answer for half the cost. And the rules arrive from one source instead of being reconstructed from the code with a risk of getting them wrong.
The second barrier: the client asks permission to call
MCP tools are third-party code, so on the first call the client asks a human whether it may run them. In an ordinary session that is a dialog: you choose «allow» and it works from then on (the «always allow» option is remembered in the project settings). But in a non-interactive run — claude -p "…", a CI run, any script — there is nowhere to show the dialog and nobody to press the button.
The cure is to list the tools at startup, or to grant the permission once in an ordinary session, after which non-interactive runs work without flags. The names are built from the server name in your configuration: call the server reviewgate and the tools are mcp__reviewgate__review_changes and mcp__reviewgate__get_team_rules.
# one-off: list the tools at startup
claude -p "check the uncommitted changes" \
--allowedTools mcp__reviewgate__review_changes mcp__reviewgate__get_team_rules
# once and for all: grant the permission in an ordinary session
# and the client remembers it for this project
claudecall the get_team_rules tool and show me what it returnedIf the rules arrive and the call is visible in the answer, the whole chain works. The tool is free and calls no model. No call means no review, whatever the answer looks like.
One subtlety we burned ourselves on: clients read their configuration at startup, so checking right after writing it is pointless — restart the session. Otherwise you will see either a refusal on a correct setup or, worse, a success from a setup that was on the machine earlier.
Every client has its own mechanism: the flag shown here is from Claude Code; in other agents look for «tool permissions» or «allowed tools». One thing is common to all of them: a server that was not allowed to work is exactly as silent as a server that was never connected — and the only way to tell the two apart is whether a call appears in the protocol.
What happens during a run
Reviews go one at a time: a second call waits for the first rather than sharing its budget and the provider limits. The channel stays free meanwhile — the short protocol methods answer instantly, so the client can see the server is alive.
Cancellation works. The agent aborted the call (the user pressed Esc, the task was dropped) — the run stops and no answer is sent for the cancelled call. An honest boundary: an answer the model has already begun will be billed by the provider either way; the saving is that the next calls never start — the judge, the ensemble, the arbiter. The same holds when the connection closes: with no client there is nobody to compute for.
A tool error arrives with isError: true, and the text holds the same reviewgate.error/v1 object with the code the CLI prints (the full list is on the CLI page). The agent has something to branch on and does not need to parse the message with regexps.
chcp 65001 in the console, otherwise non-ASCII characters in the report — the emoji markers among them — come out garbled. It does not affect --json: stdout is always UTF-8.