Reference
The complete configuration, command, and environment surface for Destesi Code Review. Every key and value below is drawn from the product’s config parser — unknown versions and out-of-range enum values fail loud rather than being silently ignored.
.destesi.yml
Section titled “.destesi.yml”A single .destesi.yml at your repo root configures everything the Destesi GitHub App does on the repo. Code review reads it from your default branch.
version: 1 # required; only 1 is recognised
defaults: # cross-cutting, optional ignore: paths: [...] branches: [...]
review: # the code-review product block enabled: true # required to opt in triggers: { ... } scope: { ... } instructions: [...] output: { ... } chat: { ... } model: { ... }
# preview: { ... } # the Preview product's block — configured separatelyTop level
Section titled “Top level”| Key | Type | Default | Notes |
|---|---|---|---|
version |
int |
— | Required. Must be 1; any other value is rejected with an invalid version error. |
defaults.ignore.paths |
[]string |
[] |
Glob patterns. Matching files are excluded from review. Additive with review.scope.paths.exclude. |
review
Section titled “review”| Key | Type | Default | Notes |
|---|---|---|---|
enabled |
bool |
false |
Must be true to opt the repo in. Without it the block is a no-op. |
The remaining keys are grouped by concern, below.
review.triggers
Section titled “review.triggers”When the reviewer acts on a PR. If you set none of the three, the defaults below apply.
| Key | Default | Effect when true |
|---|---|---|
on_open |
true |
Initial review fires on opened, reopened, and ready_for_review. |
on_push |
false |
Re-review on every push. Off by default so chatty PRs don’t burn budget. |
on_command |
true |
Respond to @destesi commands regardless of on_push. |
triggers: on_open: true on_push: false on_command: truereview.scope
Section titled “review.scope”Which PRs, and which files within them, are eligible. Triggers say when; scope says which.
scope: drafts: false # skip draft PRs (default) base_branches: ["main"] # empty = all base branches paths: include: [] # empty = all files (still subject to ignore lists) exclude: [] # additive to defaults.ignore.pathsPath priority: ignore beats include. A file matching defaults.ignore.paths or scope.paths.exclude is reported as ignored; a file not in a non-empty include list is reported as out_of_scope. Both surface in the summary’s “skipped files” footer.
review.instructions
Section titled “review.instructions”Per-path natural-language guidance fed to the model. A list of { paths, guidance } entries; multiple matches stack in declaration order, so a file can pick up several instruction blocks.
instructions: - paths: ["**/*.go"] guidance: | Wrap returned errors with %w. Flag goroutines without a context.
- paths: ["api/internal/**"] guidance: | Be strict about auth boundaries on control-plane routes.| Field | Type | Notes |
|---|---|---|
paths |
[]string |
Glob patterns. The instruction applies when the changed file matches any glob in the list. |
guidance |
string |
Free-form guidance prepended to the per-file prompt for matching files. |
review.output
Section titled “review.output”What the reviewer writes back to GitHub.
| Key | Type | Default | Allowed values | Notes |
|---|---|---|---|---|
summary |
bool |
true |
— | Top-level walkthrough comment, edited in place on re-runs. |
inline |
bool |
true |
— | Per-hunk comments, batched into one PR review. |
check_run |
bool |
true |
— | The “Destesi Review” entry in the Checks tab. |
nits |
string |
collapsed |
surface · collapsed · hide |
How nit-severity comments render. hide drops them entirely from the inline review. |
fail_check_on |
string |
severe |
never · severe · any |
When the Check goes red. See below. |
approve_on |
string |
never |
never · clean |
clean submits a GitHub “Approve” event when the review finds zero inline comments. |
How fail_check_on maps to the Check conclusion:
| Policy | Highest finding severity | Check conclusion |
|---|---|---|
never |
anything | success |
severe (default) |
none | success |
severe |
nit or warn | neutral |
severe |
severe | failure |
any |
none | success |
any |
any finding | failure |
A neutral Check does not block merges; only failure does. The reviewer never submits a formal “Request changes” — gating is the Check’s job.
review.chat
Section titled “review.chat”chat: allowed_associations: [OWNER, MEMBER, COLLABORATOR] commands: [review, summary, ignore, help]| Key | Type | Default | Allowed values |
|---|---|---|---|
allowed_associations |
[]string |
[OWNER, MEMBER, COLLABORATOR] |
OWNER · MEMBER · COLLABORATOR · CONTRIBUTOR · FIRST_TIME_CONTRIBUTOR · NONE (GitHub’s author_association values) |
commands |
[]string |
[review, summary, ignore, help] |
Any subset of the four commands. |
review.model
Section titled “review.model”LLM selection and cost budgets. The reviewer runs two kinds of call: one summary call per PR, and one inline call per changed file (the fan-out). Both default to the same model — see below for why. Omit the whole model block to use platform defaults.
model: summary: provider: bedrock # anthropic | bedrock | platform name: global.anthropic.claude-sonnet-4-5-20250929-v1:0 inline: provider: bedrock name: global.anthropic.claude-sonnet-4-5-20250929-v1:0 max_files: 25 # changed files sent to the inline pass max_file_bytes: 16384 # head content per file, per prompt max_hunk_bytes: 131072 # diff hunks per file, per prompt max_diff_bytes: 2097152 # whole-PR diff accepted from GitHub max_input_tokens: 36864 # per-file prompt budget concurrency: 4 # max parallel inline calls per review| Key | Type | Default | Notes |
|---|---|---|---|
model.summary.provider |
string |
bedrock |
anthropic · bedrock · platform (or empty). platform lets the environment decide. |
model.summary.name |
string |
global.anthropic.claude-sonnet-4-5-20250929-v1:0 |
Provider-specific model identifier. Only filled when both provider and name are omitted. |
model.inline.provider |
string |
bedrock |
Same allowed values as summary.provider. |
model.inline.name |
string |
global.anthropic.claude-sonnet-4-5-20250929-v1:0 |
Provider-specific model identifier. Only filled when both provider and name are omitted. |
model.max_files |
int |
25 |
Changed files sent to the inline pass. Ceiling 50. |
model.max_file_bytes |
int |
16384 (16 KiB) |
One file’s head content inside one inline prompt. |
model.max_hunk_bytes |
int |
131072 (128 KiB) |
One file’s diff hunks inside one inline prompt. |
model.max_diff_bytes |
int |
2097152 (2 MiB) |
Whole-PR unified diff accepted from GitHub before parsing. |
model.max_input_tokens |
int |
derived | Per-file prompt budget in tokens, enforced as an extra clamp over max_file_bytes + max_hunk_bytes. With the default byte budgets that works out to ~36,864 tokens. |
model.concurrency |
int |
4 |
Max parallel inline calls (and file-content fetches) per review. Ceiling 8. |
Why the summary model is not the expensive one: the summary pass never sees your source or your diff. It reads the one-sentence digests the inline pass produced and writes at most 600 words of markdown, so it runs on Sonnet like the inline pass does. Review quality lives in the inline pass, which is the one that actually reads the code. Set model.summary.name if you want a heavier model back.
A single review covers at most 25 changed files by default; files beyond that cap are reported as file_limit in the summary footer and are not reviewed at all. See Some files were skipped for the full set of drop reasons.
Glob syntax
Section titled “Glob syntax”paths lists everywhere in the file use the same glob dialect:
| Pattern | Matches |
|---|---|
* |
Any run of characters except / |
? |
Exactly one character except / |
** |
Any sequence including / — zero or more path segments |
**/*.go |
Every Go file at any depth |
api/internal/** |
Anything under api/internal/ |
**/generated/** |
Anything under any generated/ directory |
Writing effective instructions
Section titled “Writing effective instructions”Per-path instructions are the single biggest lever on review quality. A few patterns that work, drawn from real configs:
- Be concrete and verifiable. “Write good Go” gives the model nothing. “Wrap returned errors with
%w. Reject silent error swallowing.” gives it a clear yes/no test per rule. - Lead with the highest-impact rule. The first rule in a
guidanceblock is the most reliably applied. Put your “if you see this, scream” rule first. - Anchor to your invariants. “This package owns auth boundaries — never approve a route without its auth middleware” is a check the model can run against any diff.
- Stack general → specific. Broad rules under broad globs (
**/*.go), surgical rules under narrow globs (api/internal/auth/**). They compose at review time. - Mark style as nits explicitly. If you don’t say “(nit)”, the model decides severity itself and often over-warns on taste.
Patterns that go wrong:
- Walls of text. Past roughly ten rules in one
guidanceblock, marginal rules get dropped. Split by glob. - Conflicting rules across overlapping globs. If
**/*.goand a narrower glob disagree, say so explicitly: “(overrides the general rule)”. - Asking for repo-wide reasoning. The model sees the changed file and the diff, not the whole repo. “Make sure no other caller broke” is wishful — write a test instead.
Chat commands
Section titled “Chat commands”Repo collaborators drive the reviewer by mentioning @destesi at the start of a line in any PR comment.
| Command | Where | What it does |
|---|---|---|
@destesi review |
Top-level PR comment | Re-runs the review on the latest recorded commit. |
@destesi summary |
Top-level PR comment | Reposts the latest review’s summary. |
@destesi ignore [reason] |
Reply to one of the bot’s line comments | Stops flagging that rule on that path going forward. Optional reason is stored. |
@destesi help |
Anywhere | Posts the command list. A bare @destesi does the same. |
Notes on behaviour:
- The mention must be at the start of a line (after optional whitespace) so it doesn’t trigger on quoted text from earlier comments.
@destesi-botand other trailing-identifier forms do not trigger. @destesi reviewre-runs against the head SHA from the PR’s most recent review. If the reviewer has never seen the PR (no recorded head SHA), it asks you to push a commit or wait for the auto-review.- How
@destesi ignoreworks: every inline comment the reviewer posts ends with a hidden marker, e.g.<!-- destesi:rule_fingerprint=a1b2c3d4 path=api/foo.go -->. When you reply@destesi ignore, the reviewer reads that marker off the parent comment and persists an override keyed by(installation, repo, path, rule_fingerprint). The override survives re-reviews. If you reply to something that isn’t one of its own line comments, it tells you it can’t read the rule id.
The reviewer deliberately does not answer free-form questions, submit “Request changes”, or push commits to your repo.
Re-review limits
Section titled “Re-review limits”@destesi review re-runs the review against the pull request’s current head commit. You may order 3 re-runs per commit (a deployment may raise this as far as 20). Past that, the reviewer replies on the pull request saying it has already re-reviewed this commit the maximum number of times — it does not fail quietly, because a command that appears to do nothing just gets posted again.
The budget exists because re-reviewing an unchanged commit buys the same answer at the same cost. Push a commit and it resets: a new head SHA is new work. Automatic reviews (on_open, on_push) don’t consume the budget — only the command does.
Self-host environment
Section titled “Self-host environment”If you run Destesi yourself, code review is a standalone controller process plus a thin slice of routes on the Destesi API. The controller polls the API for queued reviews, fetches diffs through the GitHub App, runs the model passes, and reports results back; the API owns the GitHub App credentials and mints short-lived installation tokens on demand, so the controller never holds long-lived GitHub credentials.
API process
Section titled “API process”| Variable | Default | Purpose |
|---|---|---|
REVIEW_ENABLED |
unset | Kill switch. Empty → review is invisible to the GitHub webhook path (no PR-driven reviews, no chat dispatch). The controller-facing routes stay mounted regardless so in-flight reviews can drain after a flip-off. |
CONTROLLER_SECRET |
required | Bearer token gating the controller-facing routes. Must match the controller. Shared with the Preview controller. |
Review controller
Section titled “Review controller”| Variable | Default | Purpose |
|---|---|---|
DESTESI_API_URL |
http://localhost:9090 |
Base URL the controller calls back to. |
CONTROLLER_SECRET |
required | Must match the API. |
REVIEW_POLL_INTERVAL |
5s |
How often the controller polls for new work. |
REVIEW_WORK_LIMIT |
50 |
Max reviews fetched per poll. |
GitHub App requirements
Section titled “GitHub App requirements”Code review uses the same Destesi GitHub App as Preview. Beyond what Preview needs, it requires:
- Permission: Issues (read) — to receive
issue_commentevents on PRs (needed for chat commands). Pull Requests (write), Checks (write), and Contents (read) are already in place from Preview. - Webhook events:
issue_commentandpull_request_review_comment, in addition topull_request.
After updating the App’s registration, existing installations must accept the new permissions before chat events arrive.