Labels, Check Runs, and Mergeability
This server uses labels and GitHub check runs together to show PR state, enforce review rules, and decide when a pull request is ready to merge.
Note: You can configure these features globally in
config.yamlor per repository in.github-webhook-server.yaml. Repository-local settings override the global file.
Built-in labels
These labels are applied automatically or by PR comment commands when their category is enabled.
| Label | How it is added | What it means |
|---|---|---|
verified |
Added automatically for auto-verified users, or manually with /verified |
The PR has been marked as verified |
hold |
Added with /hold by an approver |
Blocks mergeability |
wip |
Added automatically when the title starts with WIP:, or manually with /wip |
Marks the PR as work in progress and blocks mergeability |
needs-rebase |
Added automatically when the PR branch is behind or diverged from the base branch | The PR should be rebased or updated |
has-conflicts |
Added automatically when GitHub reports merge conflicts | The PR is not mergeable until conflicts are resolved |
can-be-merged |
Added automatically when all merge rules pass | The PR currently satisfies the server’s mergeability checks |
automerge |
Added with /automerge by a maintainer or approver |
Tells the server to squash-merge once can-be-merged succeeds |
branch-<base-branch> |
Added automatically on PR open and update | Shows the target branch, such as branch-main |
size/<category> |
Added automatically on PR open and update | Shows PR size, such as size/M |
needs-rebase and has-conflicts are separate signals. A PR can be behind the base branch, have conflicts, or both.
The built-in label categories and colors are configurable:
labels:
enabled-labels:
- verified
- hold
- wip
- needs-rebase
- has-conflicts
- can-be-merged
- size
- branch
- cherry-pick
- automerge
colors:
hold: red
verified: green
wip: orange
needs-rebase: darkred
has-conflicts: red
can-be-merged: limegreen
automerge: green
approved-: green
lgtm-: yellowgreen
changes-requested-: orange
commented-: gold
cherry-pick-: coral
branch-: royalblue
Note: If
labels.enabled-labelsis omitted, all configurable built-in label categories are enabled. If you set it to[], all configurable built-in labels are disabled, but reviewed-by labels still remain active.
Reviewed-by labels
The server also creates dynamic labels that reflect review activity:
approved-<user>lgtm-<user>changes-requested-<user>commented-<user>
These labels are always enabled and cannot be disabled through labels.enabled-labels.
The distinction that matters most is this:
- GitHub’s normal “Approve review” action becomes
lgtm-<user>. - This project’s explicit
/approvecommand becomesapproved-<user>. - A
changes_requestedreview becomeschanges-requested-<user>. - A comment-only review becomes
commented-<user>.
That is what lets the server separate “looks good” from “formal approver approval”.
Tip: When new commits are pushed to a PR, the server removes existing reviewed-by labels and rebuilds mergeability from fresh review activity. This is why
approved-*andlgtm-*labels disappear after asynchronizeevent.
Size and branch labels
Every opened or updated PR gets a branch label and a size label.
The branch label uses the base branch name:
branch-mainbranch-developbranch-release-1.2
The size label is based on additions + deletions. The built-in defaults come directly from the source:
STATIC_PR_SIZE_THRESHOLDS: tuple[tuple[int | float, str, str], ...] = (
(20, "XS", "ededed"),
(50, "S", "0E8A16"),
(100, "M", "F09C74"),
(300, "L", "F5621C"),
(500, "XL", "D93F0B"),
(float("inf"), "XXL", "B60205"),
)
That means the default labels are:
size/XSfor fewer than 20 changed linessize/Sfor 20 to 49size/Mfor 50 to 99size/Lfor 100 to 299size/XLfor 300 to 499size/XXLfor 500 and up
You can replace those defaults with your own categories:
pr-size-thresholds:
Tiny:
threshold: 10
color: lightgray
Small:
threshold: 50
color: green
Medium:
threshold: 150
color: orange
Large:
threshold: 300
color: red
Massive:
threshold: inf
color: darkred
Even if you rename the buckets to Tiny, Express, or Critical, they are still controlled by the single size category in labels.enabled-labels.
Tip: Use
inffor the last size bucket so every PR larger than your biggest numeric threshold still gets a label.
Verified Check
When verified-job is enabled, the server maintains a verified check run and uses it as one of the merge requirements.
For a normal contributor, the flow is:
- A new PR starts with
verifiedinqueued. - Someone can mark it with
/verified. - The server adds the
verifiedlabel and sets theverifiedcheck tosuccess. - When new commits are pushed, the server removes the
verifiedlabel and resets the check back toqueued.
For trusted or automation accounts, the server can do this automatically:
verified-job: true
auto-verified-and-merged-users:
- "renovate[bot]"
- "dependabot[bot]"
- "trusted-user"
auto-verify-cherry-picked-prs: false
A few details matter here:
verified-jobdefaults totrue.auto-verified-and-merged-usersare auto-verified on PR open and update.- The server also adds the users behind configured GitHub tokens to the auto-verified list automatically.
- If
auto-verify-cherry-picked-prsisfalse, cherry-picked PRs are not auto-verified. - AI-resolved cherry-picks are never auto-verified, even if cherry-pick auto-verification is enabled.
If you do not want verification to be part of mergeability, set verified-job: false.
Can-Be-Merged Check
can-be-merged is the server’s final readiness check. When it succeeds, the server marks the PR with the can-be-merged label. When it fails, the label is removed and the check output explains why.
A PR must satisfy all of these rules:
- The PR must still be open and mergeable.
- No required checks can be in progress.
- No required checks can be missing or failed.
holdandwipmust not be present.- Any configured
can-be-merged-required-labelsmust be present. - There must be no blocking
changes-requested-<user>label from an approver. - Approval and LGTM rules must pass.
- If conversation resolution is enabled, there must be no unresolved review threads.
You can also add extra label-based gates:
can-be-merged-required-labels:
- "approved"
- "tests-passed"
- "security-reviewed"
The check considers the following status sources:
- GitHub required status checks from the base branch’s branch protection
- Built-in server checks such as
tox,verified,build-container,python-module-install, andconventional-title - Mandatory custom check runs
- Legacy GitHub commit status contexts
Note:
pre-commitstill runs when enabled, butcan-be-mergedonly treats it as required when it appears in the branch’s required status checks.Warning: On private repositories, the runtime
can-be-mergedlogic skips the live branch-protection status-check lookup. If you rely on extra required checks in private repos, make sure your server-side required checks and mandatory custom checks cover what you need.
If unresolved conversations are blocking the PR, the check output looks like this:
PR has 2 unresolved review conversation(s):
- src/main.py:42 (https://github.com/test-org/test-repo/pull/123#discussion_r100)
- src/utils.py:10 (https://github.com/test-org/test-repo/pull/123#discussion_r101)
The server recalculates can-be-merged automatically when:
- a relevant label is added or removed
- a required check run completes
- a terminal commit status arrives
- a review thread is resolved or reopened
- you run
/check-can-merge
Tip: If the PR also has the
automergelabel, a successfulcan-be-mergedcheck triggers an immediate squash merge.Tip: If you want the check run but not the extra label, you can disable the
can-be-mergedlabel category. Thecan-be-mergedcheck still reports the result.
Minimum LGTM And Approval Rules
minimum-lgtm does not replace approval. It adds an extra reviewer-consensus rule on top of approver approval.
Here is the distinction:
/approveis the formal approval signal and createsapproved-<user>./lgtmand normal GitHub “approved” reviews createlgtm-<user>.minimum-lgtmcountslgtm-*labels from eligible reviewers.- The PR author’s own LGTM does not count.
Example:
minimum-lgtm: 2
With that setting, the PR still needs approver approval from the relevant OWNERS rules, and it also needs at least two valid LGTMs.
The rules are OWNERS-aware:
- A root approver can satisfy the approval requirement for the whole PR.
- Otherwise, the server looks at the approver sets attached to the changed files.
- A
changes-requested-<user>label only blocks mergeability when that user is an approver for the PR.
Note: The LGTM requirement is effectively capped by the number of eligible reviewers. If
minimum-lgtmis higher than the number of valid reviewers, the server treats the requirement as satisfied once every eligible reviewer except the PR author has added LGTM.
Set minimum-lgtm: 0 to disable the LGTM requirement entirely.
Unresolved Conversation Checks
Conversation resolution is controlled by branch-protection.required_conversation_resolution, which defaults to true.
A repository-level example looks like this:
branch-protection:
strict: true
require_code_owner_reviews: true
dismiss_stale_reviews: false
required_approving_review_count: 1
required_linear_history: true
required_conversation_resolution: true
When this setting is enabled:
- the server queries GitHub review threads through GraphQL
- resolved threads are ignored
- unresolved threads block
can-be-merged - outdated unresolved threads still count
- the check output includes the file, line, and discussion URL when available
Set required_conversation_resolution: false if you want can-be-merged to ignore unresolved review threads.
Note: If you want mergeability to update immediately when a conversation is resolved or reopened, make sure the repository is configured to receive
pull_request_review_threadevents.
Custom Check Runs
Custom check runs let you add your own PR checks without changing Python code.
A schema example from this repository looks like this:
custom-check-runs:
- name: lint
command: uv tool run --from ruff ruff check
mandatory: true
- name: security-scan
command: TOKEN=xyz DEBUG=true uv tool run --from bandit bandit -r .
mandatory: false
How custom checks behave:
- The check name appears in GitHub exactly as configured.
- The command runs in the PR worktree.
- The command is executed through
/bin/sh -c, so environment variables, pipes, and other shell syntax work. - Custom checks are queued and run on PR open and PR update.
- Both mandatory and optional custom checks still run.
- Checks with
mandatory: falseare excluded from merge-blocking logic.
That makes mandatory: false useful for visibility-only checks:
- run a slow or advisory scan
- show results on the PR
- keep
can-be-mergedfocused on the hard gates
You can retry custom checks with the same commands you use for built-in checks:
/retest lint/retest your-check-name/retest all
Validation rules are strict:
nameandcommandare required- names may only use letters, numbers,
.,_, and- - names must be 1 to 64 characters
- duplicate names are skipped after the first one
- names that collide with built-in check names are rejected
- the executable must exist on the webhook server host or container
The built-in names rejected for custom checks are:
toxpre-commitbuild-containerpython-module-installconventional-titlecan-be-merged
Warning: If the executable in a custom check is not available in the webhook server environment, the server does not load that custom check. It logs a warning and skips it.
Tip: Use
mandatory: falsefor advisory checks, andmandatory: truefor checks that should blockcan-be-merged.
Useful Commands
These are the commands most relevant to labels and mergeability:
/wipand/wip cancel/holdand/hold cancel/verifiedand/verified cancel/lgtm/approve/automerge/check-can-merge/retest <check-name>/retest all
In practice, the flow usually looks like this:
- Open or update a PR.
- Let the server apply branch, size, and merge-state labels.
- Wait for check runs to finish.
- Use
/lgtm,/approve, and/verifiedas needed. - Resolve any remaining review threads.
- Watch for
can-be-mergedto turn green.