> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fullotto.app/llms.txt
> Use this file to discover all available pages before exploring further.

# otto.yml

> Configure how Otto reviews one repository, down to individual paths.

Your [workspace settings](/reviews/workspace-settings) apply to every repository. To configure a single repository, commit an `otto.yml` at its root. In it you can:

* Set the [review depth](/configuration/review-depth) for the repository and for individual paths
* Add [review instructions](/configuration/instructions) for the repository and for individual paths
* [Ignore files](/configuration/ignoring-files) such as lockfiles, snapshots, and generated code
* [Skip automatic reviews](/configuration/skipping-reviews) by label, title, branch, or author
* Limit when Otto [approves](/configuration/approval-policy)

Without an `otto.yml`, Otto reviews the repository with your workspace settings.

## Quick start

Create `otto.yml` at the repository root and merge it into your default branch:

```yaml otto.yml theme={null}
version: 1
reviews:
  paths:
    - match: ['src/payments/**']
      depth: max
      instructions: |
        Every money amount must be an integer number of cents.
  ignore: ['**/*.snap']
```

Pull requests opened after the merge use it. See [Which version applies](#which-version-applies).

## Full example

```yaml otto.yml theme={null}
version: 1
reviews:
  # false = no automatic reviews; `/otto review` still works
  enabled: true

  # Repository default depth: standard | deep | max (omit to use the workspace depth)
  depth: deep

  # Applied to every review of this repository
  instructions: |
    Prefer composition over inheritance.

  # Every matching rule contributes its depth and instructions
  paths:
    - match: ['features/**']
      depth: max
      instructions: |
        Verify every new feature is behind a feature 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
```

## 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 | *(workspace depth)* | Default depth for this repository: `standard`, `deep`, or `max`. |
| `reviews.instructions` | string | *(none)* | Guidance for every review of this repository. |
| `reviews.paths` | list | `[]` | Path rules, each with its own `depth` and `instructions`. |
| `reviews.paths[].match` | string\[] | *(required)* | Globs the rule applies to. |
| `reviews.paths[].depth` | string | *(none)* | Depth for files matching the rule. |
| `reviews.paths[].instructions` | string | *(none)* | Guidance for files matching the rule. |
| `reviews.ignore` | string\[] | `[]` | Globs Otto leaves out of the review. |
| `reviews.default_ignores` | boolean | `true` | Also ignore the [built-in list](/configuration/ignoring-files#default-ignores) 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 or Azure unique name, case-insensitive). |
| `reviews.approval.approve_up_to` | string | `P3` | The most severe finding Otto may still approve with: `P3`, `P2`, or `none`. |

Any field that takes a list also accepts a single string, so `ignore: '**/*.snap'` works too.

### Limits

| Limit | Value |
| - | - |
| File size | 64 KB |
| Rules in `reviews.paths` | 50 |
| Entries in any one list (globs, labels, …) | 100 |
| Length of one glob | 512 characters |
| Length of one skip value (label, title, author) | 256 characters |
| Length of any `instructions` | 4,000 characters |

List entries past a limit are ignored, and the rest of the list still applies. An `instructions` value over 4,000 characters is ignored entirely, not truncated. Each is reported as an error.

## Where Otto looks

Otto reads `otto.yml` from the repository root. It also accepts `otto.yaml`; if both exist, `otto.yml` wins. The file must be a regular file, not a symlink.

## Which version 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` 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.

<Tip>
  To try a change, merge it and open a test PR. Changing `otto.yml` inside an
  open PR has no effect on that PR.
</Tip>

## Globs

Globs in `paths`, `ignore`, and the branch skip rules match the file's **full path from the repository root**:

| Glob | Matches |
| - | - |
| `fixtures/**` | Everything under the top-level `fixtures` directory only |
| `**/fixtures/**` | Everything under any `fixtures` directory, at any depth |
| `**/*.snap` | Snapshot files anywhere |
| `*.md` | Markdown files in the repository root only |
| `.github/**` | Dotfiles and dot-directories match like any other path |
| `src/{api,web}/**` | Everything under `src/api` or `src/web` |

`*` stays inside one path segment; `**` crosses segments. For branches, globs match the branch name without `refs/heads/`, so `release/*` matches `release/1.2` but not `release/1.2/hotfix`.

## YAML notes

Otto parses `otto.yml` with strict, JSON-compatible YAML:

* Booleans are only `true` and `false`. `yes`, `no`, `on`, and `off` are strings, so `enabled: no` is an error and the default (`true`) applies.
* Quote globs that start with `*`, `{`, or `[`, which YAML would otherwise treat as syntax: `ignore: ['**/*.snap']`.
* Use `|` for multi-line instructions.

## Errors

A broken `otto.yml` never stops a review. Otto ignores each invalid or unknown part, keeps the rest, and uses your workspace settings for anything it ignored:

* An invalid value (for example `depth: maximum`) is ignored, and that field falls back to its default.
* An invalid entry in a list is dropped on its own; the rest of the list still applies.
* A path rule without any valid `match` glob is ignored.
* An unknown key is ignored.
* If the file isn't valid YAML, is larger than 64 KB, or can't be read, Otto reviews with your workspace settings only.

On GitHub, the **Otto Review** check summary lists the problems Otto found (up to 10, with a count of the rest), so you can fix the file.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.