Risk of Change¶
Every review carries a risk of change verdict: low, medium, high or
critical, with the reasons that produced it. It answers a different question
from the findings. A finding says "this line is wrong". The verdict says "this
change can reach a long way, so read it carefully", even when every line is
right.
The verdict is deterministic: it comes from fixed rules. It makes no model call, so it costs nothing. It reads the same on every re-run of the same head commit, so a workflow can rely on it.
Where You See It¶
The review summary line names the level and its top reason:
3 findings · risk high (`src/core/models.py` is imported by 84 files, +2 more) · provider … · model … · lgtmaybe …
The change overview lists every reason in a Risk of Change section above High Impact Areas. The summary carries the verdict even when the overview is turned off or skipped, so every review has it.
The summary also carries a hidden marker, <!-- lgtmaybe-risk:high -->. With
pr_labels on, the PR gets a risk/<level> label. Locally, lgtmaybe
review prints the same summary line.
How the Level Is Decided¶
Each factor raises the level, and the verdict takes the highest:
| Factor | Raises to |
|---|---|
| A changed file imported by 5 or more files in the repository | medium |
A changed file imported by 20 or more files, or matching a risk.core_paths glob |
high |
| Touching an infrastructure, security, data migration, or backup and recovery path | high |
| Touching a path for any other High Impact area (dependencies, compatibility, cost, …) | medium |
| 500 or more changed lines | medium |
| 50 or more changed lines of code with no test file changed | medium |
A regulated project stage |
medium |
The area rows use the same path patterns as the High Impact Areas floor, applied to changed files that are not tests or documentation.
Two distinct high reasons make the change critical: a widely imported
module and a Terraform file, say, or an auth change and a database migration.
Two files in one area count once. A change touching only documentation and
tests is low, whatever the project stage. The regulated floor never goes
above medium, so it can't combine with one other high reason to make a
change critical.
Blast Radius¶
The blast radius is how much existing code a change can reach. lgtmaybe measures it by counting the files that import each changed file.
The importer count comes from one ast-grep scan
of the checked-out repository for Python, TypeScript and JavaScript imports. It
parses files and runs nothing. On pull_request_target the checkout is the
trusted base branch, and nothing from the scan is sent to the model.
The count measures the code that already depends on a file:
- A file the PR adds has no existing importers.
- Importers the PR itself changes are left out, because they are already in the diff.
- Test files don't count as importers.
When a blast radius can't be measured, the verdict names the file instead of guessing. This happens:
- for a language the scan doesn't read;
- when there is no checkout;
- when the ast-grep binary is missing or the scan fails;
- when the scan runs past its 20-second limit.
A low verdict with unmeasured files always says so:
risk low (blast radius not assessed: `lib/billing.rb`)
The import count can't see modules reached by dynamic import, TypeScript path
aliases or barrel re-exports. Name those in risk.core_paths (see
risk).
Gating on the Verdict¶
lgtmaybe never approves or blocks a PR on its risk. The marker lets you decide
what a level means for your team. For example, a step after the lgtmaybe step
can request a second reviewer when the latest review summary carries
lgtmaybe-risk:critical:
- name: Require a second reviewer on critical changes
env:
GH_TOKEN: ${{ github.token }}
PR: ${{ github.event.pull_request.number }}
run: |
latest=$(gh pr view "$PR" --json reviews \
--jq '[.reviews[].body // "" | select(contains("lgtmaybe-risk:"))] | last // ""')
if grep -q 'lgtmaybe-risk:critical' <<<"$latest"; then
gh pr edit "$PR" --add-reviewer my-org/platform-leads
fi
Reading only the latest summary matters: each new head commit gets a fresh summary, so an older review can still carry a level the current change no longer has.
If the assessment itself fails, the summary reads risk unavailable and no
marker is written, so a gate reads a missing marker as "unknown", never as a
level.