team · access

One key for the whole team

To review locally, every developer needs a key to a model — and every new hire means setting up one more. The simpler way: the developer's CLI never calls a model at all; it sends the changes to the bot, and the bot runs the review with the same key it already uses for merge requests.

The developer authenticates with something they already carry: their own GitLab or GitHub token. Access is decided by group membership, so adding a person to the group grants it and removing them takes it away — the bot keeps no user list of its own, and there is nothing to revoke separately when somebody leaves.

How it works

The key stays on the bot, and the code comes to it. The CLI sends the diff and the changed files in full right away — they are needed for certain. Everything else the bot asks the CLI for during the review, one file at a time: .reviewgate/config.yml from the working copy, the files with findings and what they import. The CLI and the bot run the same engine, so the review is the one the merge request would get: the code goes to the same models, and the same judge checks the findings. The bot itself keeps nothing after the run: the session lives in memory while the connection is open, and the logs and metrics hold no code.

Local runs cannot take the whole bot. The bot gives developers' runs only part of its slots, one of three by default, and keeps the rest in reserve for merge requests: local runs cannot take them, however many developers start a review at once. If no slot is free, a local run waits for one, a minute by default, and when the time is up it gets a server_busy refusal. To make the wait longer, raise the bot's REVIEW_CLI_QUEUE_WAIT_MS environment variable (in milliseconds). Each developer gets one run at a time: until it finishes, the bot refuses new ones at once.

An abandoned run stops. If the developer presses Ctrl-C or the run hits the session cap (15 minutes by default, REVIEW_CLI_SESSION_TIMEOUT_MS), the bot cuts off its calls to the model instead of finishing the review at the company's expense. The metrics record such a run as cancelled. While the review runs, the CLI shows only the elapsed time: the report arrives in full at the end, and a heartbeat every 15 seconds keeps proxies from dropping the silent connection.

What the bot never sees: your git history and other repositories — the CLI serves no files outside the repository. The developer's personal GitLab or GitHub token is used only to check access: who this is, whether they are in the group, and whether they can see the project. The bot does not store it, log it, or send it to the model.

What to decide during setup

The setup is small: a few variables in the bot's environment and three lines in the developer's config, all of it with the defaults in the reference. This page covers only what you have to decide.

Who gets access. That is decided by the GitLab groups listed, comma-separated, in REVIEWGATE_CLI_GROUPS. GitLab membership is inherited downwards. Name a subgroup, and both its members and the members of its parent groups get in. Name the parent, and only its members get in: people added to a subgroup alone are refused. For the same reason, removing someone from a subgroup does not close access for a member of the parent. The role in the group does not matter, but a private project needs at least Reporter. Name a group that already exists for a reason — say, «everyone with commit rights» — rather than one created for the bot: that kind stops being updated within a month, and access stops meaning anything.

How many slots to give developers. The bot runs at most REVIEW_CONCURRENCY reviews at once — merge requests and local runs together. REVIEW_CLI_CONCURRENCY sets how many of them may be local; the remaining slots are closed to developers and always stay with merge requests. By default there are three slots, and local runs get one of them. The more slots you give developers, the less often they wait, but the smaller the reserve for merge requests. REVIEW_CLI_CONCURRENCY must be at least one less than REVIEW_CONCURRENCY, or the bot will not start, so you need at least two slots in total.

The developer's personal token. A GitLab token with the read_api scope is enough, and it need not sit in a file — the config can get it from a command. Use an https:// address for the server: the token travels with every request.

~/.config/reviewgate/config.yml
server:
  url: https://bot.your-company.com
  gitlab_token_command: pass show gitlab/token

gitlab_token_command is a command the CLI runs through the shell every time it starts; whatever it prints is the token. Any secret manager with a command line will do: pass, the macOS keychain (security find-generic-password -s gitlab -w), 1Password (op read …). The command must print the token alone, on one line: if your pass entry holds anything else, add | head -1. If both gitlab_token and the command are set, gitlab_token wins, and the REVIEWGATE_GITLAB_TOKEN environment variable beats both.

To check the setup, run reviewgate doctor inside the repository on the developer's machine. It does not merely read the config: it goes through the same access check on the server as a run would, and shows the result — who was let in, to which project and where the token came from, or the reason for a refusal. It does not check the bot's model or how busy the slots are: the first real run will show those.

Tracking spend: what you get and what you do not

If the bot has Postgres attached (DATABASE_URL), every run through the server lands in the metrics table: the developer's login, the models, the tokens and the outcome. Rows are kept for 90 days (METRICS_RETENTION_DAYS). «Who spent what this month» is one SQL query, but you count in tokens: there is no money in the table. Two gaps are worth knowing up front: abandoned (cancelled) and failed runs carry no tokens in their row, although the model may already have done paid work, and runs made with --local on a personal key are not recorded at all.

The product has no per-person spending limit — only the one-run-at-a-time rule. The only budget the product has is per run: LLM_BUDGET_TOKENS in the bot's environment, off by default. At 80 and 100 % of the limit the bot warns in the report, and it stops a review only at three times the limit: that guards against a disaster such as a looping agent, not against overspending. Quotas like «five reviews a day for this person» do not exist, and neither does a dashboard: the metrics are a Postgres table, and there are sample queries in the reference.

And the developer picks the model. The bot reads .reviewgate/config.yml from the working copy, uncommitted edits included, and that file sets the models, the judge and the make-up of the ensemble. The bot's key pays for them, so an edit along the lines of «let me give myself a stronger model» is not something the server will stop. You can only spot it after the fact, in the model columns of the metrics.

That leaves you two levers, and both are blunt. The first is the group: take a person out of it, and their next run is refused, while one already going finishes. But if the group serves more than the bot, the person loses everything else it grants along with the server. The second lies outside the product: a spending limit on the bot's key itself in the provider's console, if the provider offers one; but it stops everything at once, merge-request reviews included.

Getting the whole team on board

The key is solved; what remains is getting the review to run on people's machines at all. For that, the hooks go into the repository: the folder with them is committed like any other code, and git is pointed at it with the core.hooksPath setting. Git does not take that setting from the repository — each clone has to switch it on: git config core.hooksPath .githooks. In JS projects husky does this: its prepare script turns the hooks on at the next dependency install, and that is how our own repository works. In other stacks the same command goes into the project's setup step. Then the review arrives with the code, not with an instruction nobody reads.

For the hooks not to get in the way, they need two properties. First, if the CLI is not installed, the hook lets the commit through rather than failing — a colleague who has not installed the CLI yet must not be blocked by your hook:

.githooks/pre-commit
command -v reviewgate >/dev/null 2>&1 || {
  echo "reviewgate: binary not found — commit allowed unchecked" >&2
  exit 0
}

Second, every outcome other than «blocking findings found» lets the commit through as well. The server does not answer, the token has expired, the server is busy or the developer already has another run going — the CLI exits with code 1, and the hook writes a line about it to stderr and exits zero. Only code 2 stops the commit. That is fail-open by design: a broken review must not block the team. But for the same reason a commit that went through is no proof the review ran: only the stderr line tells one from the other.

Next