unless
The unless setting names the checks that can waive a scope's review requirement. When every listed check passes on the pull request's current head commit, the scope is waived: nothing is required, nobody is requested, no checklist is owed — and the pull request shows which check waived it.
[[scopes]]
name = "payments"
paths = ["payments/**"]
reviewers = ["$payments_team"]
require = 1
unless = ["acme-review/no-functional-change"]
Each reference is producer/check-name — the slug of the GitHub App that posts the check, then the check run's name (split on the first /, since check names can contain slashes). For a check posted by a GitHub Actions workflow, the producer is github-actions. A bare check name is a configuration error: any workflow with checks: write can post a check under any name, so the producing App is what proves the check came from who you meant.
The producer slug is case-insensitive; the check name must match exactly, including case. A wrong-case name isn't an error — it's a reference to a check that never appears, so the scope simply never waives. Copy the name verbatim from the check as it appears on a pull request.
Only a passing check waives
There is deliberately no syntax for reacting to a failed or missing check:
- Passed on the head commit → the scope is waived.
- Failed, skipped, cancelled, neutral, still running, or missing → the requirement stands, unchanged.
A push resets everything: checks are per-commit, so a new head commit means the requirement stands until the check passes again — the same way CI works.
Listing several checks means all of them must pass. There is no OR, threshold, or 2-of-3 syntax — a producer that needs logic like that should decide for itself and post one combined check.
The waived scope is the receipt
A waived scope isn't hidden. It still shows up in the workspace and the PR comment, along with which check waived it and a link to the run. So the config always shows that a scope's review can be waived (unless sits right next to the require it can remove), and the pull request always shows when that happened and why.
What PullApprove reads (and doesn't)
- PullApprove never creates these checks; it only reads them. Its own status on the pull request can't be referenced from
unless. - Reviews never waive. A bot's approval is a general-purpose verdict, not an answer to what a specific scope is asking — only a check run can waive.
- Bots are never reviewers. Bots that open pull requests are routed with
authors; bots whose checks can waive a scope go inunless. Listing a[bot]account inreviewers,alternates, orccis a configuration error. - GitHub only. Check runs are a GitHub concept, so
unlessis a configuration error on GitLab rather than a setting that quietly does nothing. - The GitHub App needs Checks: Read. Apps created before this feature existed don't have it, and every pull request whose config references a check fails with "PullApprove needs Checks (read) access to this repository" until it's granted. In your GitHub organization: Settings → Developer settings → GitHub Apps → your PullApprove app → Permissions & events → Repository permissions → set Checks to Read-only → Save, then accept the permission request on the installation. Configs without
unlessare unaffected.
Request timing
The pull request's status never waits on anything. Sending a review request is a little more patient:
If a referenced check is visibly running on the head commit, requests wait until it concludes. If it hasn't appeared at all — a fresh push often reaches PullApprove seconds before the check exists — the first send waits about half a minute for it to show up, then goes out.
The hold is per-scope: scopes without unless request immediately, so one slow check never holds a whole pull request. And once a request is sent, it stands — PullApprove never withdraws a review request, so a pass that lands after one went out simply waives the scope and the request can be ignored.
Guidance for check producers
- Use a dedicated GitHub App for a gating producer. Every Actions-posted check shares the
github-actionsproducer, so any workflow in the repo withchecks: writecan post under that producer and name. Fine for low-stakes automation in a repo you control — but a check that removes human review deserves its own App identity. - Create your check run when you start, not when you finish. A visible in-progress run is what holds review requests while you work (Actions does this automatically; an App should POST an
in_progressrun up front). A producer that's invisible until the end may find requests already sent — and sent requests stay sent. - Be as strict as the requirement you waive. If your check is nondeterministic, re-running it until it passes is a lever your policy has handed out.
FAQ
I added unless in a pull request and nothing was waived — why?
Configuration governs from the pull request's base branch, like every other setting. The pull request that introduces unless is still reviewed under the configuration already merged; the waiver takes effect once the change merges, and open pull requests on that base pick it up automatically.
Does unless work on pull requests targeting a branch other than the default?
Yes — configuration is read from whatever base branch the pull request targets. One caveat: the instant re-check on a check's completion is indexed from the default branch's configs, so on another base a check that finishes late may not waive the scope until the next event on the pull request (a push, comment, or review).
What happens when the check is re-run? The re-run replaces the previous result immediately. From the moment it's queued, the scope is un-waived and the requirement stands — the old pass doesn't count while a new answer is on the way. If the re-run passes, the scope waives again.
What if the same check name shows up twice on one commit?
A workflow with multiple triggers (push + pull_request) produces two check suites, each with its own same-named run. Every matching run must pass — if they disagree, the requirement stands. Prefer a single trigger for the producing workflow.
Someone requested changes — does a passing check still waive the scope? No. A waiver removes a requirement, never a person's objection. A scope with an open changes-requested review fails regardless of its checks.
Can a fork pull request be waived? Only by real evidence. Checks are read from the base repository, so a fork can't serve its own answer — and if the producing workflow doesn't run for fork PRs, the check is simply missing and the requirement stands. Nothing waits on it; reviewers are requested as usual.
A check passed after reviewers were already requested — now what? The scope is waived; the request stands. PullApprove never withdraws a review request, so the reviewer keeps a pending request on a pull request that no longer needs them — the waived scope in the PR comment explains why, and they're free to review anyway or ignore it.