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

FieldTypeDefaultDescription
versionnumber1Config format version. Only 1 exists.
reviews.enabledbooleantruefalse turns off automatic reviews. /otto review still works.
reviews.depthstring(org depth)Default review depth for this repository: standard, deep, or max.
reviews.instructionsstring(none)Guidance applied to every review of this repository (up to 4,000 characters).
reviews.pathslist[]Path rules with their own depth and instructions (up to 50 rules).
reviews.paths[].matchstring(required)Globs the rule applies to.
reviews.paths[].depthstring(none)Review depth for files matching the rule.
reviews.paths[].instructionsstring(none)Guidance for files matching the rule (up to 4,000 characters).
reviews.ignorestring[]Globs Otto leaves out of the review.
reviews.default_ignoresbooleantrueAlso ignore the built-in list of lockfiles, build output and generated code.
reviews.skip.labelsstring[]Skip automatic reviews of PRs with any of these labels (case-insensitive).
reviews.skip.title_containsstring[]Skip automatic reviews of PRs whose title contains any of these (case-insensitive).
reviews.skip.base_branchesstring[]Skip automatic reviews of PRs into a branch matching any of these globs.
reviews.skip.head_branchesstring[]Skip automatic reviews of PRs from a branch matching any of these globs.
reviews.skip.authorsstring[]Skip automatic reviews of PRs by these authors (GitHub login, Azure unique name).
reviews.approval.approve_up_tostringP3The 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:

  1. Each changed file gets the highest depth among the paths rules that match it. If no rule with a depth matches, the file gets reviews.depth, and if that isn't set, the organization's depth.
  2. 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:

  1. otto.yml instructions
  2. The repository's OTTO.md or CLAUDE.md
  3. 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:

GroupPatterns
Lockfilespackage-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 outputdist/, build/
Minified files*.min.js, *.min.css
Source maps*.map
Vendored codevendor/, 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: none stops Otto from approving.

Approval Policy

approval.approve_up_to sets the most severe finding Otto may still approve a PR with:

ValueOtto approves when the review has
P3No findings, or only P3 nitpicks (the default).
P2No P0 or P1 findings.
noneNever. 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.