Skip to content

Docs / .maintainer.yml reference

.maintainer.yml reference

Complete field reference for the .maintainer.yml policy file.

Drop a .maintainer.yml at your repository root to configure the maintainer bot. The file is YAML; all fields are optional and default-safe, an empty file with only version: 1 is valid.

Schema autocomplete. Add # yaml-language-server: $schema=https://developerz.ai/schemas/maintainer.v1.json as the first line of the file to get IDE validation and autocomplete.

Minimal example

# yaml-language-server: $schema=https://developerz.ai/schemas/maintainer.v1.json
version: 1

Full example

# yaml-language-server: $schema=https://developerz.ai/schemas/maintainer.v1.json
version: 1

tone:
  voice: friendly
  language: en

triage:
  ask_for_repro: true
  required_fields: [steps, expected, actual, version]
  dedupe:
    enabled: true
    similarity_threshold: 0.85
  qualify_into: [bug, feature, dupe, question]

handoff:
  mode: webhook
  webhook_ref: my-claude-code-hook
  ask_reporter_first: true

pr:
  wait_for_other_bots: [coderabbit, copilot]
  required_checks: [ci]
  large_threshold_lines: 500
  auto_merge: true
  require_review: true
  human_merge:
    branches: [main]
    paths: [db/migrations/**]

release:
  manager: release-please
  channels: [github-release]

escalate:
  always: [hostile_tone, direct_mention, paid_customer]
  ask_before_acting: [breaking_change, major_dep_bump, first_time_contributor, large_pr]

kb:
  repo_notes:
    - note: "Auth lives in packages/auth; ignore the legacy auth-old/ tree."
      by: "@maintainer"
  pages:
    - title: Architecture
      purpose: How the worker dispatches jobs.
  memory:
    stale_after_days: 90
    delete_unused_after_days: 40

review:
  profile: chill
  learnings: true
  anti_noise:
    severity_floor: minor
    max_comments: 10
  path_filters: ["!**/*.lock", "!dist/**"]

Root fields

Field Type Required Description
version 1 yes Schema version. Must be 1.
schema URL string no JSON schema URL for editor tooling.
format_command string (shell command) no Formatter the bot runs before git add on the BYOVM (e.g. biome check --write ., gofmt -w .). Non-zero exit fails the commit rather than failing CI later.

tone

Controls the bot's voice and language in all comments.

Field Type Default Description
voice friendly | formal | terse friendly Persona used in all bot comments.
greeting_template_ref string (ref name) default Name of a greeting template configured in the dashboard.
language BCP-47 string en BCP-47 language tag for bot-generated copy (e.g. en, de, pt-BR).

triage

Governs how new issues are triaged: reproduction requests, deduplication, and label outcomes.

Field Type Default Description
ask_for_repro boolean true Bot asks reporters for a reproduction before qualifying the issue.
required_fields steps | expected | actual | version | environment | logs[] [steps, expected, actual, version] Checklist items the bot requests when ask_for_repro is enabled.
non_goals string[] (max 10, 200 chars each) [] Things this project deliberately will not take, one short line each in your own words (e.g. no Windows support). Empty by default, and empty is meaningful: this list is the only thing that lets the bot decline a request as out of scope, so if you declare none it never will. When a request directly matches an entry, the bot posts one comment quoting that line as your stated position and suggesting a fork. It does not close, lock or label the issue, and it never invents or paraphrases a stance you did not write.
dedupe.enabled boolean true Similarity deduplication of new work against in-flight tasks (and their open PRs).
dedupe.similarity_threshold number 0–1 0.85 Similarity cutoff (0–1). New work scoring at or above this against an in-flight task is dropped as a duplicate and cross-linked to the surviving task.
auto_split boolean false Opt-in: when the scoped judgment comes back as multiple groups, dispatch them as several linked tasks (a stacked-PR series) instead of one. Default off. Splitting one report into N tasks spends real fleet capacity, and work in the same code area is queued rather than parallelized. It only ever narrows: a split cuts up an ask triage already admitted, it never admits one.
max_parallel_tasks integer 1–8 3 Cap on how many tasks one auto_split may produce. The cap folds, it does not truncate: groups beyond the cap are merged into the last kept task (problem, criteria and paths combined, dependencies re-aimed), so a lower value means a bigger final PR. Never dropped work. 1 means never split.
shipped_lookback_days integer 0–90 14 How far back (days) the scheduler looks for recently shipped work when placing a new report in the queue. 0 disables it. Distinct from dedupe, which only sees work in motion (open tasks and their PRs). This is the finished-work half. A match defers the report (claimed after the ordinary backlog), it never drops it: a signal that recurs after a fix shipped is a regression, not a duplicate.
qualify_into bug | feature | dupe | question | docs | chore[] [bug, feature, dupe, question] Allowed outcome labels after triage.
labels.qualification map: bug | feature | dupe | question → string 1–64 - Rename the GitHub label the bot writes for a qualification verdict (#1555); the classification never changes, only the label NAME gh.issues.label applies. Map bug / feature / dupe / question (the four verdicts the bot ever labels) to your own name, e.g. bug: type:defect. Each mapped label name is 1–64 characters; a longer or empty value is refused at parse. An omitted verdict keeps the platform default (bug / enhancement / duplicate / question), so a repo that sets none of these is unaffected.

handoff

After qualification, the bot can hand off the issue to a coding agent. mode = webhook fires a signed POST to the configured webhook; mode = label applies a GitHub label your CI or coding agent can pick up; mode = fleet dispatches directly to the developerz-hosted BYOVM fleet (the assigner claims the durable tasks row; no external webhook fires); mode = none disables handoff.

When mode = webhook, webhook_ref is required and the webhook must be registered in the dashboard. mode = fleet does not require webhook_ref.

Field Type Default Description
mode webhook | label | fleet | none none How qualified issues are handed off to a coding agent.
label string - GitHub label applied when mode = label.
webhook_ref string (ref name) - Dashboard webhook name used when mode = webhook. Not required when mode = fleet.
ask_reporter_first boolean true Bot asks the reporter for consent before handing off.

pr

Pull request review policy.

auto_merge is on by default. The bot merges once the machine gates pass: CI green, review verdict, and the repo's branch protections. Never blindly. To keep a human in the loop, set auto_merge: false or list the sensitive branches/paths under human_merge. (Free-OSS-tier repos stay opt-in regardless.)

require_review is on by default too, and it is what makes "review verdict" above a real gate: a head with no approving external review and no verdict from the built-in reviewer is handed to a human instead of merged. If your repo ships machine-gated on CI alone, set require_review: false. Deliberately, rather than by omission.

Field Type Default Description
wait_for_other_bots coderabbit | copilot | dependabot | renovate[] [coderabbit, copilot, dependabot] Bots the maintainer bot waits for before acting on a PR.
required_checks string[] [] CI check names that must pass before the bot considers merging.
large_threshold_lines integer > 0 500 PRs exceeding this line delta trigger an ask_before_acting escalation.
auto_merge boolean true On by default. The bot merges once the machine gates pass, CI green, review verdict, and branch protections satisfied. Set false (or use human_merge) to hand the merge back to a human.
require_review boolean true On by default. The bot never merges a head nobody reviewed, a review being either an approving external review (a human, or CodeRabbit) or any verdict from the built-in reviewer for that head. With no review the PR waits, then goes to a human. Set false to ship machine-gated on CI alone.
human_merge.branches string[] (globs) [] Target-branch globs held for a human even when auto_merge is on (e.g. main, release/*).
human_merge.paths string[] (globs) [] Changed-path globs held for a human even when auto_merge is on (e.g. db/migrations/**, **/*.tf).

release

Release automation. The bot can cut releases after a PR merges based on conventional commit history.

Field Type Default Description
manager release-please | none release-please Release automation driver.
channels github-release[] [github-release] Where the bot publishes when it cuts a release. github-release is the only supported value today, npm, crates, pypi and rubygems are reserved for a later release and are rejected by validation, because publishing to a registry needs a manifest version bump the bot cannot do yet. Publish to registries from your own CI on the tag the bot pushes.

escalate

Escalation rules: when the bot emails you immediately, and when it drafts an action and waits for your approval instead of taking it.

Field Type Default Description
always string[] [hostile_tone, direct_mention] Categories that immediately email the maintainer, on top of the hardcoded security, license, cla set. Writing this key REPLACES the default, it does not add to it: list every category you want kept, not only the one you are adding. Anything outside the built-in vocabulary below is rejected by validation.
ask_before_acting string[] [breaking_change, major_dep_bump, first_time_contributor, large_pr] Conditions that pause the bot and request explicit approval before continuing. Writing this key REPLACES the default, it does not add to it: one narrow entry silently turns the rest off. Anything outside the built-in vocabulary below is rejected by validation.

Always escalated, not configurable: security, license, cla. Listing them is harmless but redundant, they fire whether or not always names them.

Accepted in always: security, license, cla, hostile_tone, direct_mention, paid_customer.

Accepted in ask_before_acting: breaking_change, major_dep_bump, first_time_contributor, large_pr, release_publish, auto_merge.

Both lists are closed. A category outside them is a validation error on the whole file, and writing either key replaces its default rather than extending it.


digest

Not configurable, and there are no digest: keys. The digest is one email per account (not per repository): every repo the account installs the bot on is summarised in the same message, sent weekly, Mondays at 09:00 UTC.

cadence, day, time and timezone used to be published here and were read by nothing; they were removed rather than left as decoration. A .maintainer.yml that still carries the block is still valid; the keys are ignored, not rejected.

Choose the recipient under Email routes in the dashboard (digest.weekly), and switch the digest off entirely with the unsubscribe link in the email footer.


coding

Knobs for the on-VM coding agent (BYOVM): which model tier each class of work gets, and whether this repo's tasks may claim boxes in parallel.

Field Type Default Description
models map: class → fast|general|smart|heavy - Per-job-class capability-tier override (#510). Map a complexity class (coding / debugging_qa / general / quick) to a tier (fast / general / smart / heavy) to repoint that class of work at a stronger or cheaper model, e.g. coding: smart on a security-flavoured repo. An omitted class keeps its built-in default; consumed at dispatch by the fleet LLM resolver.

ci

Fences for the self-hosted CI runners the fleet boxes serve, plus the knob for what the bot does about a red check. The per-workflow opt-in is the label itself (runs-on: developerz-*); these repo-wide booleans sit on top of it. All are defaulted, so a repo that commits a policy but omits ci: still gets the safe fences.

Two independent halves: enabled decides whether your CI jobs run on our boxes, self_fix decides whether the bot pushes a commit at a check that went red. Neither turns off observation, the merge gate always waits for green, whoever ran the job, so the bot never merges a red pull request.

Field Type Default Description
enabled boolean true Whether the fleet places this repo's CI jobs on our boxes. The developerz-* runner labels are the opt-in per workflow; set false to stop placing CI jobs on fleet boxes without editing every workflow file. It governs placement only, check ingestion, the wait-for-green merge gate and the self-fix lane all keep running with it off. To stop the bot pushing commits at red checks, use self_fix.
self_fix boolean true Whether the bot may answer a red check by pushing a fix commit to the pull request. On by default: the bot opened the PR, so a failing check on it is its own defect to clear, and CI still has to go green again before anything merges. Set false when your CI runs somewhere else (Blacksmith, your own runners) and a red check is your engineers' to fix. The bot then still waits for those checks and never merges a red PR, it just stops pushing commits at them. Independent of enabled.
public_repos boolean false Non-negotiable public-repo fence. Off by default, a public repo lets any contributor open a PR whose workflow runs on your hardware, so the ci-assigner refuses to place a public-repo job unless a maintainer flips this on.

mcp_servers

Array of external MCP servers the agent may call. Each entry:

Field Type Default Description
name ref name string - Unique identifier for this server (used in audit logs).
url URL - Base URL of the MCP endpoint.
transport sse | http-streamable | stdio - Transport protocol.
token_ref string (ref name) - Dashboard secret name injected as Authorization: Bearer.
allowed_tools string[] ["*"] ["*"] allows all tools. Otherwise an explicit allowlist.
timeout_ms integer 1–60 000 5000 Per-request timeout in milliseconds (max 60 000).

Ref names (token_ref) resolve to secrets registered in the dashboard. The value is never stored in the policy file.

Example

mcp_servers:
  - name: linear
    url: https://mcp.linear.app/sse
    transport: sse
    token_ref: linear-api-key
    allowed_tools: [create_issue, search_issues]
    timeout_ms: 10000

integrations

Per-integration use grants (#311), the config-as-code half of the credential gateway. Connecting an integration in the dashboard stores the credential; your yml must ALSO opt in here before the agent gets the gateway tool. The agent uses the connection through a platform-mediated tool, the credential is resolved server-side per call and never enters the model's context.

Field Type Default Description
slack boolean false Allow the agent to post to the connected Slack integration's configured default channel (gateway-mediated, the bot token never enters the session, and the agent cannot pick another channel).

Example

integrations:
  slack: true # agent may post via the connected Slack workspace

tool_services

Per-repo config for the tool services the agent reaches as MCP servers. Config, never a credential: connect the service once in the dashboard and its key reaches the box as process env only, which the generated .mcp.json's ${VAR} references resolve against. Nothing here is secret, so the file stays safe to commit.

Keyed by service slug, an unknown slug is a validation error, not silently ignored. Only a service with its own config FILE takes a key here (ui-debugger's browser targets); one configured entirely by its env block needs nothing in your repo. The body is passed through verbatim to that service's own config file (.ui-debugger-mcp.json), so it is the tool's schema, not ours.

Setup mode writes the file establish-or-verify, never clobber: your existing file survives byte-identical, and a .example companion always ships so you can see the shape. Declare nothing and no live file is written, a guessed dev-server URL that 404s teaches the agent your app is broken.

Example

tool_services:
  ui-debugger:
    workspace: ./tmp/ui-debugger-mcp
    targets:
      dashboard:
        adapter: browser
        headless: true
        url: http://localhost:5173

kb

Repo-steered knowledge, a committed, reviewable way to correct or seed the agent's understanding of your repo. The bot reads it during triage alongside what it has learned on its own, so a short note here grounds answers without any code change.

kb.repo_notes

Short, attributed facts. Combined note length is capped at 10 000 characters.

Field Type Default Description
note string 1–1 000 - The fact itself (1–1 000 chars).
by string 1–120 - Who asserted it, a GitHub login, team, or name. Optional.

kb.pages

An explicit table-of-contents that overrides the agent's auto-clustering. Up to 30 pages; titles must be unique and any parent must name another page.

Field Type Default Description
title string 1–120 (unique) - Page title (1–120 chars, unique across pages).
purpose string 1–500 - One-line description of what the page covers (1–500 chars).
parent string (existing title) - Title of the parent page for a shallow tree. Must name an existing page.
notes string ≤2 000 - Optional longer prose for the page (≤2 000 chars).

kb.memory

Retention and consolidation settings for the bot's accumulated memories (learnings from past reviews, issues, and discussions). Distinct from repo_notes and pages (which are explicit, human-authored steering). Memory entries that grow stale or unused are pruned or merged automatically by the nightly gardener; these knobs tune that cleanup.

Field Type Default Description
stale_after_days integer ≥7 90 A memory entry untouched this long (days) is eligible for consolidation into a KB page.
delete_unused_after_days integer ≥7 false 40
consolidate boolean true Merge/summarize stale-but-still-referenced memory entries instead of leaving them to rot.

Consolidation. When consolidate: true, the gardener summarizes stale-but-still- referenced memories into KB pages via the account's BYOK model. When false, they are left as-is (or deleted if unused). Deletion. Set delete_unused_after_days to false to keep all memories forever; set a numeric value ≥7 to delete memories that have never been recalled (or recalled before the deletion cutoff).

kb.background

Consent + cost caps for the BYOK-billed background KB work, the one-time import that seeds your wiki when the bot is first installed, and the nightly gardener consolidation. Both spend your own BYOK tokens, so this block governs whether they run at all and how large a wiki they may grow. Leave enabled unset to take the plan default (on for paid plans, off for the free OSS tier); set it explicitly to override.

Field Type Default Description
enabled boolean plan default Whether the platform may run BYOK-billed background KB work (the initial import at install + the nightly gardener consolidation) for this repo. Leave unset to take the plan default (on for paid plans, off for the free OSS tier) or set true/false to override it.
max_pages integer 1–200 40 Cap on how many agent-maintained wiki pages the background lanes may seed and keep for this repo.

Example

kb:
  repo_notes:
    - note: "Auth lives in packages/auth; ignore the legacy auth-old/ tree."
      by: "@maintainer"
    - note: "We never accept PRs that touch the vendored/ directory."
  pages:
    - title: Architecture
      purpose: How the worker dispatches jobs.
    - title: Dispatch
      purpose: Two-phase pull queue + per-entity lock.
      parent: Architecture
  memory:
    stale_after_days: 90
    delete_unused_after_days: 40
    consolidate: true

review

Native AI code review, a hosted, CodeRabbit-compatible reviewer running on your BYOK model, included in the seat. The native review: block is a superset of .coderabbit.yaml (adds anti-noise floor/cap and KB learning write-back); when the bot reads a .coderabbit.yaml it honors most fields but defers reviews.tools and reviews.finishing_touches (surfaced as warnings).

Precedence. If a repo has both a .coderabbit.yaml and a review: block, the review: block wins (.maintainer.yml > .coderabbit.yaml). The block is optional, omit it and the bot reads your existing .coderabbit.yaml (if any) or falls back to the built-in review defaults. Migrating off CodeRabbit is a no-op: keep the file, uninstall their app.

Bot etiquette. The reviewer always defers when another review bot (e.g. CodeRabbit, Copilot, Dependabot) is active on the repo, no bot-on-bot loops. To hand review to the native reviewer, uninstall the other bot, or set review.force: true to review in parallel while migrating off CodeRabbit.

Field Type Default Description
enabled boolean true Master switch for the native reviewer.
depth inline | deep inline inline (default) (a diff-based review from the BYOK inference path. deep) the reviewer additionally runs your repo’s own build/test/lint on a checkout and folds the results into the review. deep requires an enrolled server holding a checkout of this repo: that pass runs there, and nothing on our side can stand in for it. Without one you still get a review, an inline (diff-only) one that says so on the pull request and names the reason. Never a silent downgrade.
coding_agents copilot[] [] Coding-agent identities whose bot-authored PRs are your own work item and should be reviewed instead of deferred as a competing bot. Empty by default → every bot author defers ("no bot-on-bot loops"); list copilot to review your GitHub Copilot coding agent’s PRs. Distinct from the CodeRabbit/Dependabot/Renovate defer set, only a real coding agent can be listed.
force boolean false Review even when another review bot (CodeRabbit) is active on the repo. Off by default so two bots never double-review the same PR; turn on while migrating off CodeRabbit.
profile chill | assertive chill chill posts fewer, higher-signal comments (raises the default severity floor); assertive surfaces more, including minor issues.
language BCP-47 string en-US BCP-47 language tag for review copy (e.g. en-US, de).
tone_instructions string ≤4 000 "" Free-text steering for the reviewer’s voice and emphasis.
request_changes boolean true Emit a request_changes verdict (not just a comment) when a blocking finding exists. ON by default: a blocking finding blocks the merge on GitHub, not just in our own gate. The bot dismisses its own earlier request_changes as soon as a later review of the same pull request stops asking for changes, so it can never leave a block standing on work that has been fixed. Set false to keep blocking findings advisory.
approval boolean true Let the reviewer post a real GitHub approval on a clean pass, the machine merge-gate signal ("gates are machines"). ON by default: a clean review satisfies a review-required branch protection the same way a human approval does. Set false to keep it advisory, so an approving review stays a quiet comment. Takes effect only where review runs (verified-OSS tier and up). The bot never approves a pull request it authored itself.
high_level_summary boolean true Post a single top-level walkthrough/summary comment on the PR.
learnings boolean true Write review learnings back to the repo knowledge base (kb) so later reviews cite them, the CodeRabbit-style moat feature.
chat_auto_reply boolean true Engage review comment threads automatically, reply to a human’s response and resolve the thread when it is settled. Off: the bot posts nothing and resolves nothing on your threads; it still reads them so later reviews honor decisions already made (turn learnings off too to stop that as well).
verified_tier boolean false Judge every candidate finding against the diff with a skeptical, calibrated verifier before posting; only findings the verifier confirms are posted, and refuted ones are disclosed as a count on the review body. Buys precision at a cost: each judged finding spends up to three bounded verification attempts on your own connected provider key (retries fire only on transport or malformed-response failures; ~$0.03 per PR at typical pricing), so this is opt-in.
auto_review.enabled boolean true Review PRs automatically on open/synchronize.
auto_review.incremental boolean true On a re-push, review only the new commits rather than the whole diff again.
auto_review.drafts boolean false Review draft PRs too.
auto_review.base_branches string[] [] Base branches BESIDE the default one to auto-review, as anchored regular expressions (e.g. release/.*, or .* for every branch). Your default branch is always reviewed and needs no entry. Same key and same dialect as CodeRabbit's reviews.auto_review.base_branches. A pattern that cannot be compiled safely is refused and the file fails validation.
auto_review.ignore_title_keywords string[] [] Skip PRs whose title contains any of these (e.g. WIP, DO NOT MERGE).
anti_noise.severity_floor critical | major | minor | nit minor Drop findings below this severity. Blocking findings always bypass it. Overrides the profile-derived floor.
anti_noise.max_comments integer 1–200 10 Cap on non-blocking inline comments per review. Blocking findings are always kept.
anti_noise.dedupe boolean true Collapse repeated findings against prior reviews on re-push.
path_filters string[] (globs) [] Glob filters applied before chunking; a leading ! excludes a path.
path_instructions { path, instructions }[] [] Per-glob free-text guidance the reviewer applies to matching files.
tolerated_check_failures string[] [coderabbit:rate limit, coderabbit:limit reached, greptile:rate limit, greptile:limit reached, greptile:quota exceeded] Non-blocking check-failure whitelist. Case-insensitive substrings matched against a failing check’s own reason text (its title, the head of its summary, or a status context’s description), so a red that says Rate limit exceeded reads as a quota outage (a check that never ran) rather than as evidence about the diff. Write check:reason to require BOTH (the check’s name must contain check and its reason must contain reason), which is how the built-ins are written, so a genuinely broken build whose log happens to quote a rate limit is never waved through. A bare entry matches on the reason alone. A matched check is omitted from the merge gate’s CI verdict, never re-graded as passing: a pr.required_checks entry still refuses, so tolerance can wave through an advisory check and nothing else. Omit the key for the built-ins; set [] to tolerate nothing. Each half is 3–120 characters, 20 entries max. Anything refused is reported, never silently dropped.
defer_to string[] detected Which third-party review bots this repo defers to (coderabbit, greptile, copilot). Omit the key and we defer to whichever ones we can see acting on the repo (detected, not configured), so installing CodeRabbit is enough. Name a list to narrow it; set [] to defer to nobody and always review here. This is not force: force runs our reviewer ALONGSIDE a healthy incumbent, while this says which incumbents exist at all. Where one is named and cannot review a commit (it reports a rate limit, or never arrives) we review it ourselves and the review says which reviewer was unavailable. Detection currently observes CodeRabbit only, so naming greptile or copilot alone means nothing is detected and we review every PR here; the other two are accepted today so a repo can declare its intent before we can see them act.

Anti-noise. Quiet by default (their #1 complaint): findings below anti_noise.severity_floor are dropped, volume is capped at max_comments, and repeats are deduped on re-push. Blocking findings (principle/gate violations) always bypass both the floor and the cap.

Example

review:
  profile: chill
  request_changes: true
  learnings: true # write review learnings back to kb
  anti_noise:
    severity_floor: minor
    max_comments: 10
  auto_review:
    base_branches: ["release/.*"] # the default branch is always reviewed
    ignore_title_keywords: [WIP, "DO NOT MERGE"]
  path_filters: ["!**/*.lock", "!dist/**"]
  path_instructions:
    - path: "packages/**/*.ts"
      instructions: "Enforce the repo principles in docs/idea/principles.md."
  tolerated_check_failures: ["coderabbit:rate limit", "quota exceeded"]

This page is generated from packages/policy/src/schema.ts. Run bun run scripts/policy-doc.ts to regenerate.