AI code review
mrsentry is built to post fewer, better comments: each one is anchored to the right line, backed by evidence, and never repeated.
-
01Gather context
Annotated diff, the enclosing code, imports, related symbols and your team's rules. No LLM call yet.
-
02Review with toolsAI
The model reads files, greps and looks up symbols to check its claims before answering.
-
03Verify the evidence
Each finding must quote the real line it's about. If the quote doesn't match, the finding is dropped.
-
04Critic passAI
A second pass removes noise and duplicates, corrects severities and writes the verdict.
-
05Publish
Inline threads on the exact lines, plus one overview note edited in place.

What the model sees
For each review unit, mrsentry builds context without calling the LLM:
- An annotated diff. Every line is prefixed with its
(old,new)line numbers, so the model reports accurate positions. - The enclosing function or class of each change, located with tree-sitter when the
treesitterextra is installed and a fast heuristic otherwise. - Imports and referenced symbols from the changed file.
- Matching guideline rules for the file type, and your team conventions from
AGENTS.md,CLAUDE.md,.mrsentry/REVIEW.md,CONTRIBUTING.md,CODING_GUIDELINES.mdanddocs/architecture.md. - The MR title and description, plus an overview of every file changed in the MR.
Files are ordered by risk. Paths that mention auth, login, tokens, crypto, sessions, permissions, payments, SQL or queries go first, then the largest changes. Files with more than review.max_file_diff_lines changed lines are reviewed one group of hunks at a time. Up to review.concurrency units run in parallel.
Agent tools
With review.agent.enabled on (the default), the model can call read-only tools before answering. For example, it can check that a function it suspects is never null-checked really isn't, or confirm where a value comes from:
| Tool | What it does |
|---|---|
read_file(path, start, end) | Reads a range of lines, with line numbers. |
list_dir(path), glob(pattern) | Explores the tree. |
grep(pattern, glob) | ripgrep if installed, with a pure-Python fallback. |
find_symbol(name) | Finds where a function, class or variable is defined. |
git(args) | Read-only show, log, diff, blame, ls-files, grep, status, rev-parse. |
Tools are confined to the checkout, and symlinks can't escape it. They are limited by review.agent.max_steps, tool_timeout_s and max_tool_output_chars. Findings confirmed with tools are marked verified with repo tools.
Verification and the critic pass
- Structured output. Findings come back through a forced tool call and are validated against a schema. Invalid output gets one automatic repair round. Models without tool calling use a JSON-text fallback.
- Evidence check. Each finding must quote the code on its line. If the quote doesn't match the real line, the finding is dropped and recorded in the JSON report with the reason.
- Thresholds. Findings below
review.min_confidenceare dropped, as are findings belowreview.min_severity. The exception is security findings, which are kept down to low. Categories outsidereview.categoriesare dropped too, but security, bug and anything rated high or above are always kept. - Critic pass (
review.summarizer). A second model call, which can use a different profile, removes noise and duplicates, corrects severities, and writes the overall summary and verdict. - Cap. At most
review.max_inline_commentsinline comments are posted, highest severity first. The rest go into the overview.
Suggestions
When the model can write a drop-in replacement for the commented lines, mrsentry posts a GitLab suggestion block, so the fix can be applied with one click. Fixes that can't be applied directly are shown as plain code. Turn this off with review.suggestions: false.
Re-runs and thread lifecycle
- Every finding has a fingerprint built from the file, category, normalized evidence and guideline. It's stored in a hidden marker in the comment, and a finding that already has a thread is never posted again.
- If a finding no longer appears when its file is re-reviewed, the bot replies "No longer detected" and resolves the thread (
review.resolve_fixed). - With
review.incremental(the default), files whose diff is byte-identical to the last reviewed version are skipped. Their open findings still count in the overview, unless a person resolved the thread. - Use
--fullto re-review everything, andmrsentry clearto remove all bot notes from an MR.
Guidelines
Guidelines are short rules with ids that the reviewer applies, and findings cite them (guideline_id). Bundled packs: general, java, python, typescript. Add your own in .mrsentry/guidelines.yml:
- id: ACME-SQL-01
title: Use the query builder for dynamic SQL
severity: high
category: security
files: ["src/**/*.py"]
description: Raw SQL strings with interpolation are forbidden; use db.query().where(...).
bad: cur.execute(f"SELECT * FROM t WHERE id = {id}")
good: db.query(T).where(T.id == id)
- id: PY-005 # same id as a bundled rule: overrides it
disabled: true # ...or turns it off
Choose which packs load with review.guidelines. Entries can be bundled:<name>, repo:<path> or an absolute path. Free-form team instructions go in review.extra_instructions, and the comment language is set with review.language.
Per-repository settings
A repository can tune the review with its own .mrsentry.yml. Because that file comes from the MR branch, only behaviour keys are accepted: categories, severity and confidence thresholds, comment cap, include/exclude globs, guidelines, extra instructions, language, suggestions, draft handling and LGTM notes. See Security model for the full list.
# .mrsentry.yml in your repository
review:
min_severity: high
max_inline_comments: 10
exclude: ["**/migrations/**", "**/*.generated.ts"]
extra_instructions: |
We use Result types instead of exceptions in src/core. Flag new raise statements there.