Review configuration (otto.yml)
Your organization's review settings (depth and review instructions) apply to every repository. To configure a single repository, commit an otto.yml file at the repository root. In it you can set the review depth and instructions per path, ignore files, skip automatic reviews, and limit when Otto approves.
Without an otto.yml, Otto reviews the repository with your organization's settings, as it does today.
Quick Start
# otto.yml
version: 1
reviews:
paths:
- match: ['features/**']
depth: max
instructions: |
Verify every new feature is behind a LaunchDarkly flag.
ignore: ['**/*.snap']
Which version of otto.yml applies
Otto reads otto.yml from the pull request's base commit: the merge-base of the PR branch and its target branch. It never reads the PR's own copy, so a pull request can't change how it is itself reviewed. A PR that sets enabled: false or lowers depth in otto.yml is still reviewed with the configuration it started from.
After you merge an otto.yml change into the target branch, it applies to:
- New pull requests.
- Existing pull requests once they are rebased onto, or merge in, the updated target branch.
If both otto.yml and otto.yaml exist, Otto uses otto.yml.
Configuration Reference
| Field | Type | Default | Description |
|---|---|---|---|
version | number | 1 | Config format version. Only 1 exists. |
reviews.enabled | boolean | true | false turns off automatic reviews. /otto review still works. |
reviews.depth | string | (org depth) | Default review depth for this repository: standard, deep, or max. |
reviews.instructions | string | (none) | Guidance applied to every review of this repository (up to 4,000 characters). |
reviews.paths | list | [] | Path rules with their own depth and instructions (up to 50 rules). |
reviews.paths[].match | string | (required) | Globs the rule applies to. |
reviews.paths[].depth | string | (none) | Review depth for files matching the rule. |
reviews.paths[].instructions | string | (none) | Guidance for files matching the rule (up to 4,000 characters). |
reviews.ignore | string | [] | Globs Otto leaves out of the review. |
reviews.default_ignores | boolean | true | Also ignore the built-in list of lockfiles, build output and generated code. |
reviews.skip.labels | string | [] | Skip automatic reviews of PRs with any of these labels (case-insensitive). |
reviews.skip.title_contains | string | [] | Skip automatic reviews of PRs whose title contains any of these (case-insensitive). |
reviews.skip.base_branches | string | [] | Skip automatic reviews of PRs into a branch matching any of these globs. |
reviews.skip.head_branches | string | [] | Skip automatic reviews of PRs from a branch matching any of these globs. |
reviews.skip.authors | string | [] | Skip automatic reviews of PRs by these authors (GitHub login, Azure unique name). |
reviews.approval.approve_up_to | string | P3 | The most severe finding Otto may still approve with: P3, P2, or none. |
Every field accepts a single string where a list is expected, so ignore: '**/*.snap' works too.
Full Example
# otto.yml
version: 1
reviews:
# false = no automatic reviews; `/otto review` still works
enabled: true
# Repository default depth: standard | deep | max (omit to use the org depth)
depth: deep
# Applied to every review of this repository
instructions: |
Prefer composition over inheritance.
# Every matching rule contributes
paths:
- match: ['features/**']
depth: max
instructions: |
Verify every new feature is behind a LaunchDarkly flag.
- match: ['docs/**', '**/*.md']
depth: standard
ignore: ['**/*.snap', 'fixtures/**']
default_ignores: true
# Automatic reviews only; an explicit `/otto review` always runs
skip:
labels: ['no-otto']
title_contains: ['[skip otto]', 'WIP']
base_branches: ['release/*']
head_branches: ['dependabot/**']
authors: ['renovate[bot]']
approval:
approve_up_to: P3
Globs
Globs match the file's full path from the repository root, so fixtures/** matches only the top-level fixtures directory. Use **/ to match at any depth: **/*.snap matches snapshot files everywhere. Dotfiles match like any other file, so .github/** works.
Branch globs in skip.base_branches and skip.head_branches match the branch name without refs/heads/. * stays inside one path segment, so release/* matches release/1.2 but not release/1.2/hotfix. Use ** to cross segments.
Review Depth
Otto picks one depth for the whole review:
- Each changed file gets the highest
depthamong thepathsrules that match it. If no rule with adepthmatches, the file getsreviews.depth, and if that isn't set, the organization's depth. - The review runs at the highest depth of any changed file. Ignored files don't count.
otto.yml can raise or lower the depth. For example, a PR that only touches docs/** in the full example above runs at standard, even if the organization's depth is deep.
If Otto support pinned a specific model for your organization, that pin still applies over otto.yml.
Instructions and Precedence
reviews.instructions applies to every review of the repository. A path rule's instructions apply only when the PR changes at least one file that matches the rule, and Otto sees which changed files the rule matched.
When guidance conflicts, the most specific source wins:
otto.ymlinstructions- The repository's
OTTO.mdorCLAUDE.md - Your organization's review instructions
None of these can change Otto's review format, its P0–P3 severity scale, or its merge-confidence score.
Ignored Files
Otto leaves ignored files out of the changed-file list and the diff it reviews. It can still read them for context, for example to check how a generated type is used. Ignored files also don't count toward the 100-file limit for automatic reviews.
Default Ignores
With default_ignores: true (the default once you have an otto.yml), Otto also ignores:
| Group | Patterns |
|---|---|
| Lockfiles | package-lock.json, npm-shrinkwrap.json, yarn.lock, pnpm-lock.yaml, bun.lock, bun.lockb, Cargo.lock, Gemfile.lock, composer.lock, poetry.lock, Pipfile.lock, uv.lock, go.sum, packages.lock.json, Podfile.lock, pubspec.lock, mix.lock, flake.lock |
| Build output | dist/, build/ |
| Minified files | *.min.js, *.min.css |
| Source maps | *.map |
| Vendored code | vendor/, node_modules/, third_party/, third-party/ |
| Generated code | __generated__/, *.generated.*, *.pb.go, *_pb2.py, *_pb2_grpc.py |
Each pattern matches at any depth. Set default_ignores: false if your repository keeps source code in one of these places, for example a build/ directory of build scripts.
Skipping Automatic Reviews
enabled: false and the skip rules apply to automatic reviews: new pull requests, new pushes, and review requests. They never block an explicit /otto review or /otto re-review comment, which runs a full review with the rest of otto.yml (depth, instructions, ignores and the approval policy).
When a skip rule matches, Otto completes the run without reviewing, at no cost. It marks the Otto Review check (GitHub) or the otto/review status (Azure Repos) as skipped, and names the rule that matched, for example skip.labels: no-otto. On Azure Repos, Otto also comments on the pull request, and edits that one comment on later pushes instead of adding another.
Otto Approval merge gate. If your organization turned on the Otto Approval check, a skipped review doesn't satisfy it: a skipped PR needs a human approval to merge, the same as a PR that is too large to review automatically. The same applies when
approve_up_to: nonestops Otto from approving.
Approval Policy
approval.approve_up_to sets the most severe finding Otto may still approve a PR with:
| Value | Otto approves when the review has |
|---|---|
P3 | No findings, or only P3 nitpicks (the default). |
P2 | No P0 or P1 findings. |
none | Never. Otto comments instead, and a human approves. |
Otto enforces this when it posts the review, not only in its instructions: if it would approve with a finding above the limit, the review is posted as a comment instead. A finding without a severity counts as above the limit.
Errors
A broken otto.yml never stops a review. Otto ignores each invalid or unknown part, keeps the rest, and uses your organization's settings for anything it ignored. If the file isn't valid YAML, Otto reviews with your organization's settings only. The Otto Review check summary on GitHub lists every problem it found, so you can fix the file.
