The gating pipeline
What happens between a contributor clicking “Create pull request” and the pull request being closed, labelled, checked and scored. This page describes the GitHub App path. The Actions path runs the same decision function and is covered at the end.
Events that trigger a run
The webhook handler acts on six pull_request actions and ignores everything else.
| Action | What it does |
|---|---|
| opened | Full run. |
| reopened | Full run. |
| ready_for_review | Full run. |
| synchronize | Full run, and the stored Check Run ids are cleared because the head SHA moved. |
| labeled | Only when the label is the project's labelEvaluate, default contribution:evaluate. Any other label, including the bot's own, exits before touching the database. |
| closed | Records whether the close was terminal. A merge or a human close ends the pull request; a close the bot performed stays reopenable. |
Adding the evaluate label by hand is the manual re-run. The label is always removed afterwards, whether or not labels are enabled, so it does not retrigger on the next event.
Precedence
decideForRepo returns one of six statuses. The checks run in this order and the first match wins.
- Repository inactive.
IGNORED. The installation was removed. - Checker disabled.
checkerEnabledis false, so the result isAPPROVEDwith the reasonchecker_disabled. The pull request is not closed and no pending or denied label is applied. Whether a row is written at all depends ontrackWhenDisabled. - Manual denial. A maintainer denied this login directly.
DENIED, and the pipeline stops here. A denied contributor is never asked to sign a CLA. - Bypass list. The login matches a glob in
bypassHandles, for example*[bot].BYPASSED, and the pipeline stops here, which is what exempts bots from the CLA and DCO gates. - Manual approval.
APPROVED, but not returned yet. The CLA layer below still applies. - Collaborator.
bypassCollabsis on and GitHub confirms the author is a collaborator.BYPASSED, cached for five minutes. If the collaborator call fails, the failure is logged and the pipeline falls through to the application check rather than guessing. - Application. The author's latest application for this project decides it. Approved means approved. Denied with no resubmission allowed, or denied inside a cooldown, means
DENIED. Denied with the cooldown elapsed becomesPENDING. Submitted but undecided isPENDING. No application at all isPENDING.
If applicationRequired is off, a missing or undecided application no longer blocks. An existing denial still does.
What each outcome does
| Status | Pull request | Comment | Label | Decision check |
|---|---|---|---|---|
| APPROVED | Left open | None | approved | success |
| APPROVED (checker_disabled) | Left open | None | approved | success |
| BYPASSED | Left open | None | approved | success |
| PENDING | Closed | Apply link, or awaiting review | pending | action_required |
| CHECK_REQUIRED | Left open | Sign the CLA, or add the sign-off | cla-pending | action_required |
| DENIED | Closed | Link to status page, no reason | denied | failure |
| IGNORED | Nothing | None | None | None |
The status check
One Check Run named contribution-checker / decision, published against the pull request's head SHA. Require it in branch protection and an unapproved contributor cannot merge, even if someone reopens the pull request by hand.
Every state below is produced by the same function the webhook calls.
Approved contributor for Acme Router.
Contribution checker is currently disabled for Acme Router; PR auto-approved.
PR author matches the project's bot bypass list.
PR author is a repo collaborator and is bypassed.
Open an application for Acme Router to unblock this PR.
Your application for Acme Router is awaiting reviewer action.
Sign the Acme Router CLA to unblock this PR. Your PR stays open and we'll re-check automatically once signed.
A new version of the Acme Router CLA must be signed. Sign it to unblock this PR. Your PR stays open and we'll re-check automatically once signed.
One or more commits are missing a Developer Certificate of Origin sign-off. Add a "Signed-off-by" trailer to each commit (e.g. `git commit -s`). Your PR stays open and we'll re-check automatically.
Denied until 2026-09-01.
Projects with a CLA get a second, independent check named contribution-checker / cla. It reports the author's signature coverage directly rather than the overall decision, so it can be required on its own.
Order of operations
For a single pull request event, convergePr runs this sequence.
- Decide.
- Load the project's labels, gates and quality settings. Exit if the project is gone.
- Apply the DCO layer, which may override the decision.
- Work out the standalone CLA state, separately from the decision, so the CLA check stays accurate on paths where the decision short-circuited earlier.
- Write or update the
PrCheckrow. An active CLA or DCO gate always writes one, even when tracking is otherwise off, because the re-check sweep finds affected pull requests by their gate reason. - Create the labels if they are missing.
- Apply the outcome: reopen, or close with a comment, or comment and keep open.
- Publish the decision check, then the CLA check.
- Run quality scoring, if it is enabled and a row exists.
Every step is idempotent, so a redelivered webhook converges to the same state instead of stacking duplicate comments. The gate comment is posted once per gate reason. The quality warning comment is claimed atomically before it is sent, so concurrent deliveries cannot double-post.
Nothing after step 5 can fail the request. Each side effect is caught and logged. The handler returns 200 unless the signature is invalid, because a non-200 makes GitHub retry the delivery indefinitely.
Approval, denial, revocation
On approval
Every pull request the bot closed for that author, across every linked repository in the project, is reopened with a comment naming the project, relabelled to approved, and marked approved. Both pull requests closed as pending and those closed as denied are reopened. The flag that says “we closed this” is what makes it safe: pull requests the contributor closed themselves are never touched.
On denial
Previously closed pull requests are not reopened. They get the denial comment and the denied label, and stay closed.
On revocation
Optional. When the maintainer ticks the box, currently open pull requests from that contributor are closed with a comment linking to their status page. The reason is not published.
Running it from GitHub Actions
If you cannot install a GitHub App, two workflow files do the same job from inside the repository.
Requests carry the OIDC token GitHub mints for the workflow. The server checks its signature against GitHub's JWKS and then trusts two claims: aud, which must be your instance's URL for that exact project, and repository, which must be a repository registered under that project. There is no shared secret to configure or rotate, and a leaked token is worthless after about six minutes.
The gate workflow uses pull_request_target so the token exists for fork pull requests. It never checks out the pull request head. Only the base branch's workflow file runs, which closes the usual pull_request_target injection hole.
The exact YAML is generated per project. Copy it from the project's Repos tab.
When something breaks
The gate is built to fail open rather than trap a contributor.
| Failure | Result |
|---|---|
| Collaborator lookup errors | Logged, falls through to the application check. |
| Commit fetch for DCO errors | DCO passes. |
Installation lacks checks:write | No check published, everything else runs. |
| Comment, label, close or check call errors | Logged, the handler still returns 200. |
| Invalid webhook signature | 401, nothing runs. |