Code review config
Every field, default, and constraint in code review pipeline YAML files.
Gathers context up front and hands it to every reviewer. Any one of them can cancel the review.
The reviewers, in parallel. Each reads the unreviewed commits in its own sandbox and proposes findings.
One gatekeeper judges every finding. What it rejects is recorded but never reaches the pull request.
Ellipsis posts one pull request review, anchored at the head commit. This step is not configurable.
Follow-up work after the review is posted, such as notifications. A failure never affects the review.
The field-by-field reference for kind: code_review pipeline files. The conventional path is agents/code_review.yaml; any config directory works, because ellipsis.kind is what identifies the file, not its location.
Code review is enabled with one organization-wide setting, on the Settings tab of /reviews. No file is required: with the setting on, the built-in pipeline reviews every pull request. A committed pipeline file customizes that, and every field except ellipsis is optional, so the file declares only what it changes.
# ─── IDENTITY ─────────────────────────────────────────────────────────
ellipsis: # the ONLY required block; everything below is optional
kind: code_review # REQUIRED literal. absent kind == "agent", so a
# pipeline that forgets it parses as an agent
# config and dies on unknown stage keys
version: v1 # only v1 supported
name: Code review # display name
description: Reviews every PR # display description
enabled: true # false = skipped at dispatch (no run row, no
# sessions). still counts as "the customer has
# intent", so it suppresses the platform default
# rather than falling through to it
metadata:
labels: [backend] # freeform; excluded from the config sha, so
annotations: {owner: platform} # retagging doesn't change pipeline identity
# ─── WHAT TO WATCH ────────────────────────────────────────────────────
pull_requests: # the `pull_request` react filter set, minus `on:`,
# promoted to the top level (the whole file is
# about PRs, so there is no `trigger:`). matched
# on every head advance: the open + each push.
# DECLARING THIS MAKES IT AUTHORITATIVE — a PR it
# does not match gets NO review, not the default
repositories: # the WATCH scope, by name. a bare list == include
include: [splitshift-api] # [] = every repo of the installation
exclude: [splitshift-docs] # exclude-only = "all except these", and stays
# correct as repos are added later. independent of
# sandbox.repositories (the PR's repo is always
# cloned)
base: [main, "release/*"] # target branch: exact / "prefix*" / "default"
head: [] # source branch, same matching; [] = all
draft: false # null = don't care; true/false to require
labels: [] # ANY-of match; [] = all
paths: [] # changed-file globs over the FULL PR file set.
# include-only — a leading "!" is a validation
# error
for: # the audience gate, on the PR author
users: true # true = all humans / a list of logins / false = none
bots: false # defaults: humans yes, bots no
# each side also takes {include: ..., exclude: []}
# ─── PIPELINE-WIDE SANDBOX DEFAULTS ───────────────────────────────────
sandbox: # the SAME sandbox block an agent config uses,
# deep-merged field-wise into each stage agent's
# own `sandbox:` — so "just change the image"
# keeps the built-in reviewer
repositories:
- name: splitshift-shared # extra repos to clone (the PR's repo is automatic)
variables:
- name: DATADOG_API_KEY # no `value` = resolved from the write-only store
- name: LOG_LEVEL
value: info # an inline literal
compute:
cpu: 2 # vCPU, fractional allowed, 0.125-16
memory: 8GB # "8GB"/"512MB" or {gb: 8}; 512MiB-64GiB
timeout: 30m # "30m"/"1h" or {minutes: 30}; 60s-1h (only shortens)
image:
# RUN-only layers on the managed base image. Runs before any repo exists,
# and is folded into the image cache key.
dockerfile_append: |
RUN apt-get update
# Runs once at image-build time AFTER checkout and before the snapshot, so
# installs are baked in and cost nothing on a cache hit. 600s cap. Never
# write secrets to disk (files are snapshotted, env is not).
setup: |
poetry install
hooks:
post_start: env > /tmp/env # per-session, never cached. 300s cap. a non-zero
post_clone: npm run codegen # exit is LIFECYCLE_HOOK_FAILED, and never retried
github:
permissions: read_only # "read_only", or an explicit scope map like
# {contents: read, pull_requests: write}.
# enforced by GitHub at token mint, not by us
repositories: [splitshift-api] # narrow which repos the token may touch
ports: [3000] # the quick-pick set for a live sandbox. irrelevant
# to stage sessions, which tear down per turn
# ─── STAGE 1: pre_review ──────────────────────────────────────────────
pre_review: # unset = inherit the platform default (empty
# today). [] = explicitly disabled. THAT
# DISTINCTION IS THE WHOLE OVERLAY MECHANISM
- name: linear-context # unique across the ENTIRE file: it keys the stage
# row, attributes findings, and titles the stage
# in the reviews UI
# The same `claude` block an agent config uses. `system` takes inline text,
# a {file: path} ref, or a list interleaving both.
claude:
system: |
Fetch the Linear issue this branch names and summarize its
acceptance criteria for the reviewers.
model: claude-opus-5 # the default
effort: high # low|medium|high|xhigh|max; unset = model default
fallback_model: claude-sonnet-5
max_turns: 30
settings: # a repo-file ref to a Claude Code settings.json
path: .claude/settings.json
sandbox: # overrides the pipeline-level block, field-wise
compute: {cpu: 1}
skills: # Claude Code skills installed at ~/.claude/skills/
- path: .agents/skills/pr-conventions
- path: skills/release-notes
repository: {name: platform-skills} # cross-repo
budget: 2.00 # this agent's per-session USD cap. unset = the
# platform divides budget.run across every agent
pull_requests: # per-agent narrowing, so a specialist never pays
base: [main] # for a sandbox just to conclude "nothing for me
head: [] # here". evaluated at MINT and pruned off the
paths: [] # frozen config. ONLY these three: not
# repositories (the pipeline owns the watch
# scope), and not labels/draft/for — those are
# properties of the PR, so keying a reviewer on
# them means the PIPELINE should have been scoped
#
# writes outbox/context.md -> handed to every
# reviewer as a query section
# writes outbox/skip.md -> CANCELS the whole run,
# and the watermark still advances (a deliberate
# "there is nothing here to review")
# ─── STAGE 2: review ──────────────────────────────────────────────────
review: # a declared list REPLACES the built-in reviewer
# wholesale. max 8 agents — a validation error,
# never a silent trim
- name: migrations # the same stage-agent shape as above
# `system` is ONLY the reviewing brain. the scope lines and the outbox
# contract are injected server-side — never restate them here.
# This agent writes outbox/findings.jsonl + review.md, and is told
# explicitly NOT to post to GitHub or any MCP tool: the platform owns
# the single commit-anchored post.
claude:
system: |
Review database migrations for production safety.
model: claude-opus-5
pull_requests:
paths: ["sql/**"] # the field that makes a specialist cheap
include_default_reviewers: false # true = splice the built-in reviewer in FRONT of
# your `review:` instead of replacing it. exists
# so "the built-in PLUS a migration specialist"
# doesn't require copying our prompt into your
# file, where it stops tracking ours. rejects a
# name that collides with a built-in, and a
# total over 8. contradicts `review: []`.
# SPENT BY THE MERGE: written back to false on
# the frozen config, so re-merging is idempotent
# ─── STAGE 3: filter (the gatekeeper) ─────────────────────────────────
filter: # ONE agent, as a MAPPING rather than a list.
# `filter: []` = no gatekeeper, and every finding
# posts unjudged. a non-empty list is rejected:
# the platform posts exactly one review, so a
# second arbiter has nothing to arbitrate
name: gatekeeper
# Reads inbox/findings.jsonl (the deduped union of every reviewer's findings)
# and writes outbox/verdicts.jsonl + review.md.
claude:
system: |
Approve only findings a staff engineer would raise in review.
# The default model. The gatekeeper decides what the customer actually
# sees, and verifying a claim against real code is the same job as
# finding it, so it is not worth doing cheaply.
model: claude-opus-5
# ⇩ the platform POSTS here: one GitHub review, commit_id = the head SHA.
# deliberately NOT expressible in YAML — that commit anchor is what advances the
# incremental watermark, so no agent may ever own the post
# ─── STAGE 4: post_review ─────────────────────────────────────────────
post_review: [] # runs AFTER the review posts. its failures never
# touch the posted review. the same stage-agent
# shape. the default is empty: a default that
# writes to every PR is too loud
# ─── SPEND ────────────────────────────────────────────────────────────
budget: # USD. deep-merges field-wise with the default
run: 10.00 # ONE pipeline iteration across ALL stages, not per
# stage. divided across the agents that declare
# no budget of their own. validated: when EVERY
# agent declares one, the sum must fit under this
day: 100.00 # this pipeline's trailing 1-day cap. checked at
week: 400.00 # dispatch BEFORE a run row exists — it is the
# push-storm guard, since one push means N
# sessionsFor how the pipeline behaves at runtime, see How to review every pull request as commits land. This page documents the fields.
The built-in pipeline
With the setting on and no file committed, this is what runs. One reviewer reads the unreviewed commit range and proposes findings, then a gatekeeper checks each claim against the code and decides what posts. Written out as YAML, the default is:
ellipsis:
version: v1
kind: code_review
name: Code review
description: Built-in incremental pull request review pipeline.
review:
- name: bugs
claude:
model: claude-opus-5
system: |
Find the bugs in this change before they reach production.
Read the diff, then read enough of the surrounding code to know
whether each change is correct. Cover correctness, edge cases,
error handling, concurrency, security, regressions in behavior
callers depend on, and data or deploy hazards. Skip style,
formatting, and naming. If you cannot name the input or state
that produces a failure, it is not a finding.
filter:
name: gatekeeper
claude:
model: claude-opus-5
system: |
Decide which proposed findings the author sees. Reject anything
that does not hold against the code as it stands, is already
handled elsewhere, is a style preference, or is speculative.
Four sharp comments are worth more than twelve mediocre ones.
budget:
run: 10.00Both stages run on the frontier model. Finding a real bug and verifying one against the code are the same job, and both decide what a customer reads on their pull request, so neither is worth doing cheaply.
The system prompts above are abridged: the shipped ones are considerably longer, and they are the reviewing brain only. The commit range under review and the file contract each stage delivers through are injected by the platform, so a prompt you write never restates them.
You do not need this file. Copy it only if you want to edit the prompts, and remember that a copied prompt stops tracking improvements to ours; include_default_reviewers adds to the built-in reviewer instead.
pull_requests
Which pull requests this pipeline watches. Matched on every head advance of a pull request: the open, plus each later push. All filters must pass; an omitted filter passes everything.
A committed file makes these filters authoritative. A pull request the file does not match gets no review at all, rather than falling back to the built-in pipeline. Scoping review to two repositories means "and nothing else".
| Field | Type | Default | Meaning |
|---|---|---|---|
repositories | repository filter | all | Repositories to watch. Independent of sandbox.repositories: the triggering repository is always cloned into every stage sandbox. |
base | list of strings | [] (all) | Target branch patterns. |
head | list of strings | [] (all) | Source branch patterns. |
draft | boolean | unset (either) | true reviews only drafts; false only non-drafts. |
labels | list of strings | [] (all) | Review only pull requests carrying at least one of these labels. |
paths | list of strings | [] (all) | Changed-file globs, matched against the full pull request file set. |
for | account selector | humans only | Who may trigger a review, classified on the pull request author. |
repositories
An include-minus-exclude filter over repository names. A bare list is shorthand for include. An empty or omitted include means every repository of the installation, so an exclude-only filter reviews everything except the listed repositories, including repositories added to the organization later.
pull_requests:
repositories:
exclude: [sandbox-experiments]Branch patterns
base and head entries take three forms:
- An exact branch name:
main. - A prefix glob:
release/*matches every branch underrelease/. - The literal
default, which matches the repository's default branch whatever it is named.
labels
A pull request passes if it carries at least one listed label. Label names match exactly, including case: Needs-Review in the file does not match a label named needs-review.
Labels are read when the review triggers, on the open or a push. Adding a label to an open pull request does not trigger a review by itself; the pipeline sees the label on the next push.
paths
Include-only globs over the pull request's changed files. The pull request passes if any changed file matches any pattern. Negated patterns (!foo) are a validation error.
- A pattern containing a slash is relative to the repository root:
src/*.pymatchessrc/main.pybut notsrc/deep/main.py. **crosses directories:src/**/*.pymatches both.- A pattern without a slash matches at any depth:
*.lockmatchesCargo.lockandvendor/Cargo.lock; a bare directory name likemigrationsmatches every file under anymigrations/directory. - A trailing slash covers the directory's subtree:
sql/matchessql/migrations/0042.sql. - A leading slash anchors to the repository root:
/README.mdmatches only the top-levelREADME.md.
Renamed files count under both their old and new paths.
for
Who may trigger a review, decided by the pull request author: the GitHub account that opened the pull request, not the author or pusher of any individual commit. One pull request has one author for its whole life, so every push to it gets the same answer. A commit pushed by someone else onto an excluded author's pull request is not reviewed, and a bot's commit onto an included human's pull request is.
users selects human accounts and bots selects bot accounts; each is an include-minus-exclude filter, and the author matches if they are in include and not in exclude.
| Field | Type | Default | Meaning |
|---|---|---|---|
users | account filter | all humans | Which humans trigger reviews. |
bots | account filter | no bots | Which bots trigger reviews. |
Each filter's include takes true (all), a list of logins (exactly those), or false or [] (none); exclude is a list of logins subtracted from the include set. A bare true, false, or list is shorthand for include, so users: true and bots: ["renovate[bot]"] work as written.
pull_requests:
for:
users:
include: true
exclude: [release-shim]
bots: ["renovate[bot]"]sandbox
Pipeline-wide sandbox defaults, applied to every stage agent's session. Takes the same fields as an agent config's sandbox block: repositories, variables, image, hooks, compute, and github. See the agent config reference for each.
A stage agent may declare its own sandbox, which is merged field-wise onto this one.
The most common use is cross-repository context. pull_requests.repositories is the watch set; sandbox.repositories is what gets cloned alongside the triggering repository:
sandbox:
repositories:
- name: splitshift-sharedpre_review
Agents that run before the reviewers, one session each. Each writes context that is handed to every reviewer, and any one of them can cancel the run entirely, which skips the review without error.
By default no pre_review agents run. Each entry is a stage agent (see Stage agents).
review
The reviewers. Each reads the unreviewed commit range in its own sandbox and proposes findings; the platform collects, deduplicates, and posts them as one pull request review.
Declaring review: replaces the built-in reviewer wholesale. Unset, the one built-in reviewer runs: bugs, a frontier-model pass briefed to find defects (see The built-in pipeline). A pipeline may declare at most 8 reviewers, and each name must be unique across the file.
Reviewers run in parallel, so several cost latency only once. Declare more than one when they have genuinely different jobs, such as a specialist scoped by paths to the code it knows.
review:
- name: house-rules
claude:
system: |
Review the diff against the conventions in CONTRIBUTING.md.
Comment only where the diff departs from a documented rule.include_default_reviewers
include_default_reviewers: true adds your reviewers to the built-in one instead of replacing it. The built-in runs first, then yours. The 8-reviewer cap counts both, and a declared reviewer may not reuse the built-in name (bugs).
Use it when the built-in review is fine and you want one more pass alongside it. Without it, keeping the built-in review would mean copying our prompt into your file, where it stops tracking ours.
Contradictory combinations are validation errors: include_default_reviewers: true with review: [] is rejected.
Stage agents
Every entry in pre_review, review, filter, and post_review is a stage agent, which runs as an ordinary agent session:
| Field | Type | Default | Meaning |
|---|---|---|---|
name | string | required | Unique across the whole file. Attributes findings and titles the stage in the reviews UI. |
claude | mapping | platform defaults | Same as an agent config's claude: system, model, effort, and the rest. |
sandbox | mapping | the pipeline's sandbox | Merged field-wise onto the pipeline-level block. |
skills | list | [] | Same as an agent config's skills. |
budget | number (USD) | split evenly | This agent's per-session cap. Agents without one split the remainder of budget.run. |
pull_requests | mapping | unset (all) | This agent's own narrowing: base, head, and paths only. |
A stage agent's pull_requests decides which of the pipeline's pull requests this one agent reads. An agent that filters out costs nothing: no sandbox starts, and it takes no share of the run budget. repositories, labels, draft, and for are not accepted here; those scope the pipeline.
review:
- name: migration-safety
claude:
system: Review database migrations for production safety.
pull_requests:
paths: ["**/migrations/**"]filter
The gatekeeper: one agent that judges every reviewer finding and decides what posts. Findings it rejects are recorded but never reach the pull request.
It is a second, independent read of every claim, not a second reviewer. It goes looking for nothing of its own; it checks each proposed finding against the code and drops the ones that do not hold.
Three shapes:
- Unset: the built-in gatekeeper runs, on the frontier model, since it decides everything you see.
- A single agent mapping: your gatekeeper replaces the built-in one.
filter: []: no gatekeeper runs, and every reviewer finding posts unjudged. The posted review says no gatekeeper ran.
filter:
name: strict-gate
claude:
system: |
Approve only findings a staff engineer would raise in review.
Reject style opinions and anything a linter catches.filter is exactly one agent, not a list. A list with entries is a validation error; [] is the disable idiom.
post_review
Agents that run after the review posts, one session each. Use them for follow-up work such as notifications. A post_review failure never affects the posted review.
By default no post_review agents run. Each entry is a stage agent.
budget
Spend limits in USD. All three are optional and independent.
| Field | Default | Meaning |
|---|---|---|
run | 10.00 | Caps one pipeline iteration: every stage agent of one review pass, together. Stage agents without their own budget split the remainder evenly. |
day | unset | This pipeline's trailing daily spend. Checked before a review starts; a pipeline over its cap reviews nothing until the window rolls. |
week | unset | Trailing weekly spend, same behavior. |
When every stage agent declares its own budget, their sum must fit under budget.run; that contradiction is a validation error.
budget:
run: 25.00
day: 100.00
week: 400.00