Reference

Code review config

Every field, default, and constraint in code review pipeline YAML files.

01
pre_review

Gathers context up front and hands it to every reviewer. Any one of them can cancel the review.

none
02
review

The reviewers, in parallel. Each reads the unreviewed commits in its own sandbox and proposes findings.

3 reviewers
03
filter

One gatekeeper judges every finding. What it rejects is recorded but never reaches the pull request.

1 gatekeeper
PLATFORM
the review posts

Ellipsis posts one pull request review, anchored at the head commit. This step is not configurable.

always
04
post_review

Follow-up work after the review is posted, such as notifications. A failure never affects the review.

none

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
                                #   sessions

For 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.00

Both 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".

FieldTypeDefaultMeaning
repositoriesrepository filterallRepositories to watch. Independent of sandbox.repositories: the triggering repository is always cloned into every stage sandbox.
baselist of strings[] (all)Target branch patterns.
headlist of strings[] (all)Source branch patterns.
draftbooleanunset (either)true reviews only drafts; false only non-drafts.
labelslist of strings[] (all)Review only pull requests carrying at least one of these labels.
pathslist of strings[] (all)Changed-file globs, matched against the full pull request file set.
foraccount selectorhumans onlyWho 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 under release/.
  • 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/*.py matches src/main.py but not src/deep/main.py.
  • ** crosses directories: src/**/*.py matches both.
  • A pattern without a slash matches at any depth: *.lock matches Cargo.lock and vendor/Cargo.lock; a bare directory name like migrations matches every file under any migrations/ directory.
  • A trailing slash covers the directory's subtree: sql/ matches sql/migrations/0042.sql.
  • A leading slash anchors to the repository root: /README.md matches only the top-level README.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.

FieldTypeDefaultMeaning
usersaccount filterall humansWhich humans trigger reviews.
botsaccount filterno botsWhich 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-shared

pre_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:

FieldTypeDefaultMeaning
namestringrequiredUnique across the whole file. Attributes findings and titles the stage in the reviews UI.
claudemappingplatform defaultsSame as an agent config's claude: system, model, effort, and the rest.
sandboxmappingthe pipeline's sandboxMerged field-wise onto the pipeline-level block.
skillslist[]Same as an agent config's skills.
budgetnumber (USD)split evenlyThis agent's per-session cap. Agents without one split the remainder of budget.run.
pull_requestsmappingunset (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.

FieldDefaultMeaning
run10.00Caps one pipeline iteration: every stage agent of one review pass, together. Stage agents without their own budget split the remainder evenly.
dayunsetThis pipeline's trailing daily spend. Checked before a review starts; a pipeline over its cap reviews nothing until the window rolls.
weekunsetTrailing 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