Skip to content

Configure .lgtmaybe.yml

Place a .lgtmaybe.yml file at the root of your repository to control how lgtmaybe reviews pull requests. CLI flags override file values; the file provides defaults for all runs.

Contents

Full example

provider: openai
model: gpt-5.5
min_severity: low
include_paths:
  - "src/**"
  - "lib/**"
exclude_paths:
  - "**/__pycache__/**"
  - "**/*.min.js"
max_files: 30
max_input_tokens: 80000
categories:
  - security
  - correctness
  - tests

Field reference

See Reference: Config for the full schema with types and defaults.

provider

Which LLM backend to use. One of openai, openrouter, anthropic, zai, bedrock, vertex, azure, ollama, openai-compatible.

provider: anthropic

model

The model identifier for the chosen provider. Format varies by provider:

Provider Example model IDs
openai gpt-5.5
anthropic claude-sonnet-4-6, claude-haiku-4-5
openrouter anthropic/claude-sonnet-4-6
zai glm-4.6, glm-4.7, glm-4.5-air (GLM / Zhipu AI; newer glm-5.x pass through too)
bedrock us.anthropic.claude-sonnet-4-6, us.anthropic.claude-haiku-4-5 (prefer the cross-region inference profile; a non-Bedrock id like openai.gpt-5.5 is invalid — see Review with Bedrock)
vertex gemini-3-pro, gemini-3.5-flash
azure your deployment name, e.g. my-gpt-4o-deployment (not the upstream model id — see Review with Azure)
ollama qwen3.6:27b, gemma4:e4b
openai-compatible the served model name, e.g. deepseek-chat or meta-llama/Llama-3.1-8B-Instruct (requires api_base — see Use a custom OpenAI-compatible endpoint)

min_severity

The minimum severity level to report. Findings below this threshold are suppressed. Ordered low to high: info, low, medium, high, critical.

min_severity: medium   # suppresses info and low findings

Default: low (suppresses only info findings).

include_paths / exclude_paths

Glob patterns to restrict which files in the diff are reviewed. include_paths acts as an allowlist; exclude_paths acts as a denylist applied after the allowlist, so an exclude always wins. Both default to empty (all files included). Patterns match against the full repo-relative path, and a **/-prefixed pattern also matches at the repo root (so **/*.lock covers a root-level lockfile). The built-in skip filter for generated, vendored, and binary files runs first either way — an include_paths entry can't resurrect a lockfile.

include_paths:
  - "src/**"
exclude_paths:
  - "src/generated/**"
  - "**/*.lock"

max_files

Maximum number of changed files to include in the review. Files beyond this limit are skipped. Reduces token usage on large PRs.

max_files: 30

Default: 50.

max_input_tokens

Token budget per model call. When the compressed diff exceeds this limit, lgtmaybe splits it across multiple batched calls (and, with recursive on, walks an over-budget single file hunk-by-hunk) — nothing is truncated or dropped.

max_input_tokens: 80000

Default: 100000.

preset

How many model calls the review spends. fast (the default) covers security, correctness and stated intent, performance, complexity, ponytail, and deprecation in four calls when parallelism is available: correctness splits into flow/intent and state/lifecycle tasks. With one worker it stays combined for three calls. full restores tests and documentation and runs one focused call per lens for release branches and deep audits.

preset: full

Default: fast. CLI: --preset fast|full (--full is shorthand). An explicit categories list (below) overrides the preset grouping.

categories

Which review lenses to run. An explicit list disables the preset grouping: the reviewer asks for each listed category in its own concurrent model call and merges the findings, so a focused prompt concentrates on one concern at a time. One or more of security, correctness, deprecation, tests, documentation, performance, complexity, intent, ponytail. Narrowing the list trades thoroughness for fewer model calls (and lower token usage).

The ponytail lens is the "lazy senior dev" check — the best code is the code you never wrote — flagging code that needn't exist at all (YAGNI, reach for the standard library, do it in fewer lines). See What gets reviewed.

The intent lens checks the diff against the PR's stated intent — title, description, and commit names on GitHub; your git log commit names on the CLI (in both branch and --working mode). When nothing states an intent (e.g. no commits beyond the base branch yet), it is skipped automatically, so it never costs an extra call (under the fast preset it shares correctness's call). It is also the only lens that sends the PR title/description/commit names to the provider — drop it from categories if you don't want that text sent at all.

categories:
  - security
  - correctness

Default: all nine categories.

context_lines

Ceiling on the number of unchanged lines added around each changed hunk. The lines are read from the head revision of the file, so the model reviews a change in the context of its surrounding code. The pad is asymmetric: the full budget goes before the hunk (the enclosing signature and setup explain a change best) and a quarter of it — at least one line — goes after. The actual number used is the smaller of this ceiling and what the token budget allows, so it shrinks automatically on large PRs. Set it to 0 to disable context expansion and review the bare diff (no extra file content is fetched).

context_lines: 10   # at most 10 lines before each hunk (2 after); 0 disables

Default: 20.

function_context

Extend each hunk's leading pad up to the enclosing function or class signature when it sits above the fixed context_lines window — the signature and setup explain a change better than an arbitrary cut. Boundaries are found structurally with ast-grep (already bundled for symbol resolution; parsing only, never executing) for Python, JS/TS/TSX, Go, Rust, Java, and Ruby, with a bounded reach so a distant definition can't drown the diff. Unsupported languages and any ast-grep failure keep the plain fixed-line pad.

function_context: false   # fixed-line padding only

Default: true.

timeout

Per-request timeout in seconds for each model call. Left unset, lgtmaybe picks a provider-aware default: 300 s for ollama and openai-compatible (local models are slow) and 60 s for cloud providers. Set it explicitly to raise it for a large local model.

timeout: 900   # 15 minutes per call, e.g. for a big model on CPU

Default: auto (ollama/openai-compatible 300 s, cloud 60 s). See Run locally with ollama.

structured_output

Constrain the model to emit the findings JSON schema using the provider's native JSON mode (litellm response_format). This keeps models — especially local ones — from returning prose or reasoning instead of findings. Leave it on unless a particular model/provider rejects response_format (some openai-compatible gateways return a 400), in which case turn it off; the lenient parser still strips fences and pulls JSON out of any surrounding prose. CLI: --no-structured-output.

structured_output: false   # only if your gateway rejects JSON-schema mode

Default: true. See Use a custom OpenAI-compatible endpoint.

prompt_cache

Cache the static system prompt across the per-lens review calls and the reflection call. lgtmaybe fans out one model call per lens, and every one of those calls shares the same large, static system prompt — on providers with an explicit cache breakpoint (anthropic, and bedrock Claude/Nova models) lgtmaybe marks that prompt with cache_control so every call after the first reads it from the provider's prompt cache at the cached-input discount instead of re-paying full input price. The diff and other per-PR content always stay outside the cached region.

Support is feature-detected per model, and on every other provider (ollama, openai-compatible, and providers that cache automatically server-side like OpenAI) the request is sent unchanged — so leaving it on costs nothing. Turn it off only to rule caching out while debugging provider behaviour. CLI: --no-prompt-cache.

prompt_cache: false   # send every call uncached, even on anthropic/bedrock

Default: true.

reflect

Run the self-reflection pass that audits the merged findings and drops the ones the model marks low-confidence, before anything is posted. This trims false positives, so leave it on for most models. Turn it off for a weaker or local model that over-prunes and drops valid findings during the audit. CLI: --no-reflect.

reflect: false   # keep every finding; skip the false-positive audit

Default: true. To audit a weak reviewer's findings with a stronger model instead of disabling the pass, set reflect_model to that model id (it uses the same provider and credentials as model).

min_confidence

During reflection the auditor also scores each kept finding's confidence from 0 (certainly a false positive) to 10 (certain it is real), reached by actively trying to disprove the finding against the diff and the file text. Findings scored below min_confidence are dropped before posting; the surviving score is shown in the CLI output and the JSON export. A finding the auditor keeps but doesn't score always survives the threshold — a missing score never drops a real finding. CLI: --min-confidence.

min_confidence: 5   # drop findings the auditor scores 0-4

Default: 0 (no numeric filtering — reflection prunes only via its keep/drop verdicts, as before the score existed).

incremental

Commit-scoped incremental review, for the GitHub posting path. On a re-run lgtmaybe reads a hidden watermark (the head SHA its last completed review covered) from its own summary comment and reviews only the diff of the commits pushed since, instead of the whole PR — faster, cheaper, and no re-noise on code that was already reviewed. New findings post as inline comments; findings on files outside the increment stay open, and are only auto-resolved by a run that actually re-reviewed their file.

It always degrades to a full review when there is no watermark yet (first review), after a force-push/rebase (the increment would be meaningless), or if the compare fails. A failed review never moves the watermark, so no commit is ever silently skipped. Comment /review full on the PR to force a full re-review on demand.

incremental: false   # every run reviews the whole PR

Default: auto — incremental on a synchronize push (new commits on an already-reviewed PR), full review everywhere else (open/reopen, slash commands, and the local CLI, which never uses it).

static_analysis

Static-analysis fusion: run fast, deterministic linters over the changed files and feed their findings to the model as hints to confirm, contextualise, or discard. This raises recall on exactly the mechanical bugs LLMs miss without posting raw linter noise — only findings the model itself confirms are reported. Supported tools: ruff and bandit (Python), and semgrep (multi-language) when you point semgrep_rules at local rules — semgrep's registry configs need the network, which the sandbox forbids.

The tools run against the already-fetched file texts in a throwaway directory (never a checkout, never executing PR code), in a subprocess with a scrubbed environment (no proxy or credential variables) and a hard timeout. A tool that isn't installed is skipped silently — install them with pip install lgtmaybe[static-analysis], or rely on whatever is already on PATH. Tool output is treated as untrusted text: redacted and injection-wrapped before it reaches the model. CLI: --static-analysis/--no-static-analysis.

static_analysis:
  enabled: true
  tools: [ruff, bandit]        # default: ruff, bandit, semgrep
  min_severity: low            # floor on mapped tool severity (default info)
  tool_min_severity:           # per-tool overrides of the global floor
    ruff: medium               # only medium+ from ruff; bandit keeps `low`
  # semgrep_rules: .semgrep.yml  # local rules; semgrep is skipped without them

Default: enabled: false — no subprocess ever runs and behaviour is unchanged.

triage_model

Two-stage model routing so routine PRs don't pay frontier prices while risky ones still get the strong model. When set, this cheap model runs first over the compressed per-file diffs. It skips files that plainly need no review (pure formatting, trivial renames, generated churn) and scores the rest 0–10 by risk; the strong model then does the deep per-lens review only on the survivors, riskiest first. Skipped files are listed in the review summary, and /review full reviews everything on demand.

A deterministic security floor always escalates past triage, whatever the cheap model says: security-relevant paths (auth/crypto/session code, migrations, IaC, CI workflows, dependency manifests), patches carrying security-relevant tokens, files with static-analysis hits, and large hunks. Any triage failure — an unparseable verdict, a provider error — reviews everything.

All three model slots (triage_model, model, reflect_model) resolve through the same provider and credentials, so pointing them all at one ollama model costs nothing. Trade-off: cheaper, faster reviews at the risk of the triage model under-rating a subtle change; the floor and the review-when-unsure prompt bound that risk, but for maximum recall leave triage off. CLI: --triage-model.

triage_model: claude-haiku-4-5   # cheap gatekeeper; unset = no triage

Default: unset (no triage — every file gets the full review, exactly as before).

auto_describe

Post a structured PR description as a comment when a PR is opened (or reopened), before the review runs: a suggested title, the change type, a short summary, a per-file walkthrough table, and — when the PR states an intent — a "does it do what it says" check. The comment is updated in place by later /describe runs, never duplicated, and a describe failure never blocks the review. /describe posts the same structured description on demand whether or not auto-describe is enabled.

auto_describe: true

Default: false.

pr_labels

Attach labels derived from the finished review — no extra model calls:

  • review-effort/1review-effort/5 — a size estimate from the changed lines, so reviewers can gauge the PR at a glance;
  • possible-security-issue — a high/critical finding from the security lens was posted;
  • consider-splitting — the diff spans many unrelated top-level directories.

Labels are reconciled on each run (a stale review-effort/2 is removed when the score changes) and only lgtmaybe's own label families are ever touched. Best-effort: a labelling failure never fails the review.

pr_labels: true

Default: false.

finding_rules

Declarative post-processing applied to findings just before posting — the safe alternative to arbitrary post-processing hooks (rules can only filter or re-grade; no user code ever runs). Each rule has a match (all specified fields must match) and an action; rules apply in order.

Match fields: path (glob, **/-prefix also matches at the repo root), category (the lens that produced the finding — security, correctness, …, or a custom lens id), title_contains (case-insensitive substring), and min_severity (at or above). Actions: drop: true or set_severity.

finding_rules:
  # complexity nits in tests aren't worth a comment
  - match: {path: "tests/**", category: complexity}
    action: {drop: true}
  # documentation findings are informational for this repo
  - match: {category: documentation}
    action: {set_severity: info}

Default: no rules.

summary_template

Custom template for the review summary line, for teams matching a house style. Placeholders: {count} (findings posted), {provider}, {model}. A template that fails to format falls back to the built-in line.

summary_template: "🤖 {count} finding(s) · {model}"

Default: unset (the built-in N findings · provider X · model Y line).

resolve_fixed

Auto-resolve a review conversation once its finding is fixed. On a re-run, when a finding lgtmaybe raised is no longer produced and GitHub marks that thread outdated (the code under it changed), lgtmaybe posts a short ✅ Looks resolved. reply and resolves the conversation. Both conditions must hold, so a thread is never collapsed just because nearby lines shifted. Set it to false to leave conversations for manual resolution.

GitHub posting only — the local CLI review has no conversations to resolve, so it ignores this. Resolving a thread uses GitHub's GraphQL API; the default GITHUB_TOKEN (pull-requests: write, already needed to post the review) is sufficient.

resolve_fixed: false   # leave fixed conversations open for manual resolution

Default: true.

extra_lenses

Define your own review lenses ("BYO skills") that run alongside the built-in categories. Each one fans out as its own focused model call and its findings merge into the same review. A lens needs an id (unique, and not one of the built-in category names) and instructions describing what to look for; a title, plus a worked example (example_diff + example_finding, supplied together) are optional but sharply improve a small model's output.

extra_lenses:
  - id: simplify
    title: Simplify or delete
    instructions: |
      Flag code that should not exist at all. The best code is the code you never
      wrote: prefer the standard library, an existing dependency, or one line over
      a new abstraction. Call out needless wrappers, premature generality, and
      "just in case" code with no caller.
    example_diff: |
      --- a/util.py
      +++ b/util.py
      @@ -4,1 +4,3 @@
       def get_name(user):
      +    name = user.name
      +    return name
    example_finding:
      path: util.py
      line: 5
      severity: low
      title: Needless local variable
      body: The temporary adds nothing; return user.name directly.
      suggestion: "    return user.name"

Lens definitions are trusted config: they go into the system prompt, so only define them in files you control (committed .lgtmaybe.yml or repo skill files), never from PR-author content. See Add a custom review lens for a full walk-through.

Default: none.

lens_paths

Load extra_lenses from separate skill files instead of inlining them — handy for sharing a lens across repos or wiring lgtmaybe into an agent harness. Each entry is a YAML file (one lens, or a list of lenses) or a directory of *.yml / *.yaml lens files. Paths are resolved relative to where lgtmaybe runs (your repo root). Lenses loaded this way are appended to any inline extra_lenses.

lens_paths:
  - .lgtmaybe/skills            # a directory of one-lens-per-file skill files
  - team-lenses/house-style.yml # or a single file

Default: none.

CLI flag overrides

Every config field can be overridden at the command line:

lgtmaybe review \
  --provider anthropic \
  --model claude-sonnet-4-6 \
  --min-severity high

Flags take precedence over .lgtmaybe.yml.