These docs describe main; the latest release is v3.0.1 (releasing).
- A command must start a line of the comment; leading whitespace is allowed. A command mentioned mid-sentence is ignored.
- Commands and their keywords (
cancel,clear,not-planned) are case-insensitive:/LGTM cancelworks. - Label values are matched case-insensitively against the prow configuration (configuration, labeling) and applied with the casing written in the file:
/kind Bugaddskind/bug. - Commands inside Markdown code (fenced ``` / ~~~ blocks, indented code, inline
code) and blockquotes are ignored. - When a command appears on several lines of one comment every line is applied (
/kind bugand/kind cleanupadd both labels), except/milestoneand/retitlewhere the last line wins. - A
cancelon any line wins over a plain/lgtm,/holdor/approve. - Milestone titles are matched exactly.
- Prow-style aliases (
/remove-lgtm,/unhold, ...) are enabled together with their base command. Listing an alias inprow-commandsenables the whole command family: configuring only/remove-kindalso enables/kind, and only/unholdalso enables/hold. - Any other lower-case
/<key>listed inprow-commandsis a label command backed by the<key>label section of the prow configuration (configuration).
| Commands | Policy | Description |
|---|---|---|
/approve |
OWNERS approver for at least one changed file if the repo has OWNERS files, otherwise Org members and Collaborators | on a repo with OWNERS files: records the commenter's approval for the files they own and re-evaluates the approve plugin, which adds approved once every changed file is covered and posts/edits the [APPROVALNOTIFIER] comment; no GitHub review is submitted. Otherwise: the bot submits an approving review. The merge gate is evaluated right after |
/approve no-issue |
same as /approve |
same as /approve; accepted for Prow compatibility |
/approve cancel |
same as /approve |
on a repo with OWNERS files: withdraws the commenter's approval and re-evaluates (approved is removed if the files they covered are no longer covered). Otherwise: dismisses the bot's latest approval |
/remove-approve |
same as /approve |
same as /approve cancel |
/assign [@userA @userB @etc] |
anyone | Assign other users (or yourself if no one is specified). Target user must be Org Member, Collaborator, or have previously commented |
/unassign [@userA @userB @etc] |
anyone | Unassigns specified people (or yourself if no one is specified). With targets, the commenter must be Org Member, Collaborator, or have previously commented. Target must have been already assigned. |
/cc [@userA @userB @etc] |
anyone | Request review from specified people (or yourself if no one is specified). Self-cc requires Collaborator; targets must be an Org Member, Collaborator, or have previously commented. |
/uncc [@userA @userB @etc] |
anyone | Dismiss review request for specified people (or yourself if no one is specified). Self-uncc requires Collaborator; with targets, the commenter must be Org Member, Collaborator, or have previously commented. Target must already have had a review requested. |
/close |
Collaborators or the issue/PR author | closes the issue / PR |
/close not-planned |
Collaborators or the issue/PR author | closes the issue / PR with the not planned state reason |
/reopen |
Collaborators or the issue/PR author | reopens a closed issue / PR |
/lock [resolved / off-topic / too-heated / spam] |
Collaborators | locks the issue / PR with the specified reason (case-insensitive; an unknown reason fails the run without locking) |
/milestone milestone-name |
Collaborators | Adds issue / PR to an existing milestone. With no title the run fails. An unknown title fails the run with the list of available milestones |
/milestone clear |
Collaborators | Removes the issue / PR from its milestone |
/retitle some new title |
Collaborators | Renames the issue / PR. With no title, nothing happens |
/meow |
anyone | replies with a random cat image from the cat API |
/check-required-labels |
anyone | re-evaluates every require_matching_label rule on the open issue / PR at once: adds the missing needs-* labels, removes the satisfied ones. No /remove- form |
/retest |
same as /lgtm, PRs only |
re-runs the failed jobs of every GitHub Actions workflow run on the head commit that ended in failure, cancelled or timed_out; runs in progress are left alone. Reacts 🚀 on the comment; comments only when nothing failed. Needs actions: write. trigger |
/test all |
same as /lgtm, PRs only |
re-runs every completed GitHub Actions workflow run on the head commit, whatever its conclusion. trigger |
/test <workflow> |
same as /lgtm, PRs only |
re-runs the completed run(s) whose workflow name or file matches (ci, CI, ci.yml; case-insensitive); several /test lines are unioned. No match: comments the list of runs |
/test ?, /test |
same as /lgtm, PRs only |
comments a workflow / status / conclusion table of the runs on the head commit |
/ok-to-test |
same as /lgtm, not the PR author, PRs only |
approves the GitHub Actions workflow runs waiting for approval on the head commit (a first-time contributor's fork) and adds the ok-to-test label; while the label stays, pending runs are approved again on every push and by the sweep. Needs actions: write. trigger |
/auto-cc |
anyone, PRs only | runs blunderbuss: requests review from request_count OWNERS reviewers of the changed files, ignoring ignore_drafts and ignore_authors. No arguments, no /remove- form |
| Label Commands | Policy | Description |
|---|---|---|
/area [label1 label2 ...] |
anyone | adds an area/<> label(s) if it's defined in the prow configuration (configuration) |
/remove-area [label1 label2 ...] |
anyone | removes an area/<> label(s) if it's defined in the prow configuration (configuration) |
/kind [label1 label2 ...] |
anyone | adds a kind/<> label(s) if it's defined in the prow configuration (configuration) |
/remove-kind [label1 label2 ...] |
anyone | removes a kind/<> label(s) if it's defined in the prow configuration (configuration) |
/lgtm |
OWNERS reviewer or approver for at least one changed file if the repo has OWNERS files, otherwise Org members and Collaborators; not the PR author | on a PR, records the head commit as a prow/lgtm commit status, then adds the lgtm label and evaluates the merge gate right after: a clean PR merges in the same run. The label only merges while it is bound to the head; needs statuses: write. Like Prow, you cannot LGTM your own PR; the guard also applies to issues since the label has no meaning there either |
/lgtm cancel |
same as /lgtm, or the PR author |
removes the lgtm label and sets the head's prow/lgtm status to pending |
/remove-lgtm |
same as /lgtm, or the PR author |
same as /lgtm cancel |
/hold |
anyone | adds the do-not-merge/hold label (or hold.label) which prevents automatic PR merging. Also see lgtm removal on pr update |
/hold cancel |
anyone | removes the do-not-merge/hold (or hold.label) label and the legacy hold label, whichever are present, and evaluates the merge gate right after |
/unhold, /remove-hold |
anyone | same as /hold cancel |
/priority [label1 label2 ...] |
anyone | adds a priority/<> label(s) if it's defined in the prow configuration (configuration). Exclusive by default: replaces any existing priority/* labels |
/remove-priority [label1 label2 ...] |
anyone | removes a priority/<> label(s) if it's defined in the prow configuration (configuration) |
/label [label1 label2 ...] |
anyone | adds the label(s) verbatim if listed under labels: in the prow configuration (configuration). Label names containing spaces are not supported. Refuses lgtm, hold, approved, do-not-merge/* and hold.label |
/remove-label [label1 label2 ...] |
anyone | removes the label(s) if listed under labels: in the prow configuration (configuration). Refuses lgtm, hold, approved, do-not-merge/* and hold.label |
/lifecycle [frozen / stale / rotten] |
anyone | adds the lifecycle/<> label and removes any other lifecycle/*. Values come from Prow and can be overridden in the prow configuration |
/remove-lifecycle [frozen / stale / rotten] |
anyone | removes the lifecycle/<> label |
/stage [alpha / beta / stable] |
anyone | adds the stage/<> label and removes any other stage/*. Values come from Prow and can be overridden in the prow configuration |
/remove-stage [alpha / beta / stable] |
anyone | removes the stage/<> label |
/status [approved-for-milestone / in-progress / in-review] |
anyone | adds the status/<> label and removes any other status/*. Values come from Prow and can be overridden in the prow configuration |
/remove-status [approved-for-milestone / in-progress / in-review] |
anyone | removes the status/<> label |
/help |
anyone | adds the help wanted label |
/remove-help |
anyone | removes the help wanted and good first issue labels |
/good-first-issue |
anyone | adds the good first issue and help wanted labels |
/remove-good-first-issue |
anyone | removes the good first issue label |
/<key> [value1 value2 ...] |
anyone | adds <key>/<value> label(s) for any other label section <key> of the prow configuration (configuration) once /<key> is listed in prow-commands. Exclusive when the section sets exclusive: true |
/remove-<key> [value1 value2 ...] |
anyone | removes <key>/<value> label(s) listed under <key> in the prow configuration (configuration) |
/remove [label1 label2 ...] |
Collaborators | removes a specified label(s) on an issue / PR |
Every label-writing command that ran (/lgtm, /approve, /hold, /remove, /ok-to-test and the label commands below) is followed by a re-check of the require_matching_label rules (/kind cleanup clears needs-kind in the same run) and, on an open PR, by the merge gate; see events for why.
Every label command applies only labels the repository already defines (labeling); a missing label fails the run with the label(s) <names> cannot be applied because the repository doesn't have them. The /remove-<key> commands are enabled together with their base command and only remove values listed in the prow configuration (configuration), so anyone may use them. lgtm, hold, approved, do-not-merge/* and a configured hold.label are always refused by /label and /remove-label, even when listed under labels:; the run fails with <label> is managed by its own command. Use /lgtm, /hold and /approve for those, and /remove for arbitrary labels.
Failure behaviour differs per command:
/close,/reopenand/retitlesilently do nothing./lock,/removeand/milestonefail the run./lgtm,/approve,/retest,/testand/ok-to-testreply with a comment and fail the run.
On a pull request /lgtm reads the head commit, writes the prow/lgtm commit status on it
(success, "lgtm by <login> at <sha7>", linked to the comment) and only then applies the
label; a status the token cannot write (403) fails the command with
cannot bind lgtm to the commit: grant statuses: write to the workflow (or set lgtm.bind_to_commit: false)
and applies no label. Every merge path then requires the head to still carry that status; a
head pushed after the /lgtm has none, so the label is stripped with one explanatory comment
instead of merging. /lgtm cancel sets the status to pending. On an issue /lgtm only
labels. Details, the configuration flag and the upgrade note:
automatic merging.
Modelled on Prow's trigger
plugin, for the repository's own GitHub Actions workflow runs. Checks from other CI systems
are neither listed nor re-run.
| Command | Does | Runs it acts on |
|---|---|---|
/retest |
POST .../runs/{id}/rerun-failed-jobs |
completed runs with conclusion failure, cancelled or timed_out |
/test all |
POST .../runs/{id}/rerun |
every completed run |
/test <workflow> |
POST .../runs/{id}/rerun |
completed runs whose name or workflow file basename matches, case-insensitively |
/test ?, /test |
a comment | none; lists workflow, status, conclusion |
/ok-to-test |
POST .../runs/{id}/approve, then the ok-to-test label |
runs with status or conclusion action_required (awaiting approval) |
- Who may: whoever may
/lgtm(an OWNERS reviewer or approver of a changed file, otherwise an org member or collaborator). For/retestand/testthe pull request author is not excluded: an org-member author may retest their own PR./ok-to-testrefuses the author (you cannot approve the workflow runs of your own pull request): it is the trust decision. Refusals reply with a comment and fail the run, like/lgtm. - What counts as a run: every workflow run on the pull request's head commit
(
GET /repos/{owner}/{repo}/actions/runs?head_sha=<head>), except the workflow this very command runs in (the run whose name isGITHUB_WORKFLOW, the caller's workflow): re-running it would re-run the command. - Feedback: on success a single 🚀 reaction on the triggering comment, no comment. A comment is
posted only when nothing happened:
No failed GitHub Actions workflow runs on <sha7>: N in progress, M successful. Checks from other CI systems cannot be re-run here., or... are already being re-run.when every candidate answered 409 (not completed, or already re-running; those are skipped). ok-to-testis the trust marker. GitHub holds the workflow runs of a first-time contributor's public fork until a maintainer approves them./ok-to-testapproves the ones waiting on the head, then applies theok-to-testlabel (built-in, created bylabel-sync; it must exist). While the pull request carries the label, the runs waiting on every later head are approved too: by thepull_requestsynchronize/reopenedhandler when the token can write (pull_request_target, or a same-repository PR) and by thesweepotherwise (events). Removing the label by hand stops the auto-approval; nothing re-adds it. Nothing pending on the head still earns the rocket when the label was just added; with the label already present it commentsNo workflow runs waiting for approval on <sha7>.instead. The post-commandneeds-*re-check and merge gate follow, as after any label write.- Permissions: the run endpoints need
actions: write(the templates and the reusable workflow grant it). A 403 fails the run withcannot re-run workflows: grant actions: write to the workflow(cannot approve workflow runs: ...for/ok-to-test). The reaction needsissues: write/pull-requests: write; a failed reaction is only a warning. - On an issue every trigger command replies
/retest only applies to pull requests.and does nothing. /override,/skipand/retest-requiredare not supported.
/meow is opt in and calls a third party image provider (the cat API). Anyone who can comment on the repository can invoke it and consume the configured API quota, so enable it only on repositories where that is acceptable. The command must be on its own line. It is best effort: if the provider is unavailable it leaves a short note instead of failing the workflow. Only images served from the provider's own CDN are rendered.
permissions:
issues: write
pull-requests: write
jobs:
prow:
runs-on: ubuntu-latest
steps:
- uses: cncf/prow-github-actions@v3.0.1
with:
prow-commands: /meow
github-token: '${{ secrets.GITHUB_TOKEN }}'
cat-api-key: '${{ secrets.CAT_API_KEY }}'The workflow token needs issues: write or pull-requests: write to post the response, because a new repository's GITHUB_TOKEN often defaults to read only. Grant both when the same workflow handles comments on issues and pull requests.
The API key is optional; unauthenticated access is best effort and may be rate limited by the provider. When set, it is provided from a repository secret and registered for runner masking before use, and is never intentionally included in the request URL or a GitHub comment.
A simplified version of Prow's OWNERS files is supported. When the repository contains any OWNERS file, the /lgtm and /approve commands are authorized against them and /approve aggregates approvals per OWNERS file; when it contains none, org members and collaborators may use both commands. The same files drive the pull_request plugins: owners-label applies their labels:, blunderbuss requests reviews from their reviewers, and approve manages the approved label. See an example using OWNERS files.
An OWNERS file may be placed in any directory. It applies to the files in that directory and all directories below it. Which files apply to a given path is resolved the way Prow does it: walk from the file's directory up to the repository root and take the union of every OWNERS file on the way (a directory's OWNERS plus its parents'). A root-only OWNERS therefore covers the whole repository.
Setting options.no_parent_owners: true in an OWNERS file stops the walk there, so only that file (and any below it) applies and the parents' approvers and reviewers are not inherited.
On a pull request the changed files are listed (for renames both the old and the new path count) and their OWNERS are read from the current tip of the pull request's base branch, never from the pull request itself: the head branch is never consulted, so a pull request cannot grant itself approvers by editing an OWNERS file, and a pull request opened before the OWNERS files existed picks them up without a rebase (GitHub's base.sha is a snapshot from the PR's last update; the bot reads the branch instead, one GET /branches/{base} per branch per run). The [APPROVALNOTIFIER] links point at that tip.
/approve: the commenter must be anapproverfor at least one changed file; their approval then counts for the files they own and the approve plugin decides whether the whole pull request is approved. The refusal is<user> is not an approver for any changed file./lgtm: the commenter must be areviewerorapproverfor at least one changed file (Prow's lgtm rule).- On an issue there are no changed files, so the root
OWNERSof the default branch is used.
The approvers role does not grant /lgtm on its own for issues; on pull requests an approver of a changed file may also /lgtm.
Modelled on Prow's approve
plugin. It only acts on pull requests whose base branch has OWNERS files; repositories without
any OWNERS file keep the legacy /approve (a bot review, see below).
Coverage, not counting. A pull request is approved when the current approvers collectively
cover every changed file (both paths of a rename count). A user covers a file when an OWNERS file
in the file's directory or above lists them under approvers (options.no_parent_owners stops
the walk, like everywhere else). One approver may cover everything; a PR spanning sdk/ and
olm/ with disjoint approvers needs one of each.
Who is a current approver is recomputed from scratch on every evaluation from the PR's
comments and reviews; nothing is remembered between runs but the approved label itself:
| Source | Effect |
|---|---|
| the PR author | approves implicitly every file their OWNERS entries cover (approve.require_self_approval: false, Prow's default). They still cannot /lgtm their own PR |
/approve, /approve no-issue |
adds the commenter, if they are an approver of at least one changed file |
/approve cancel, /remove-approve |
removes the commenter |
a review in state APPROVED |
adds the reviewer (unless ignore_review_state) |
a review in state CHANGES_REQUESTED |
removes the reviewer, even after a /approve (unless ignore_review_state) |
/lgtm, /lgtm cancel |
count as /approve, /approve cancel only with lgtm_acts_as_approve |
| comments or reviews by bots | never count |
Comments and reviews are ordered by time; each user's latest action wins, so /approve after a
CHANGES_REQUESTED review re-adds them and a cancel after an approval removes them. A comment
carrying both /approve and /approve cancel is a cancel.
What it writes:
- the
approvedlabel, added when the PR becomes covered and removed when it stops being covered. Only written when the state changes; a human adding or removing it triggers a re-evaluation that puts it back the way the coverage says. The label must exist in the repository (label-synccreates it); - one
[APPROVALNOTIFIER] This PR is **APPROVED**/**NOT APPROVED**comment, posted on the first evaluation and edited in place afterwards (found by the hidden marker<!-- prow-github-actions/approve -->). It lists the approvers so far, who to/assignto complete the approval (chosen greedily: whoever covers the most still-uncovered files, ties alphabetically) and every OWNERS file the PR touches, struck through with its approvers once covered, bold otherwise.
Sticky. A push (synchronize) never removes approved; the PR is re-evaluated because the
changed files may differ, and the comments and reviews still stand. This is the opposite of lgtm,
which the lgtm PR job removes on every push.
Edge cases: a pull request with no changed files is not approved (there is nothing anyone
vouches for); a changed file no OWNERS file covers can never be approved and is listed as such in
the notifier. /approve cancel by someone who never approved is a no-op re-evaluation.
No bot review by default. On repositories with OWNERS files the bot submits no GitHub review
of its own accord: a review by github-actions[bot] would satisfy branch protection's "required
approving reviews" on its own, which is the wrong signal once approved is what the
merge gate requires. /approve cancel therefore dismisses
nothing; it just recomputes. A repository that wants a required review to follow the Prow decision
opts in with approve.github_review.
Events that evaluate: pull_request opened, reopened, synchronize, ready_for_review,
labeled/unlabeled of approved; pull_request_review submitted, dismissed; and the /approve family of comments.
See events.
With approve.github_review: true (default false) the token keeps
an APPROVE review on the pull request's head commit for as long as the PR carries approved,
and dismisses it when approved goes away. Humans keep using /approve (and /lgtm); a branch
protection rule or ruleset that requires one approving review is then met by the Prow decision,
without anyone approving twice. It is the plugin's verdict mirrored, not an extra human review
(required reviews and OpenSSF Scorecard).
Only repositories with OWNERS files are affected; without them /approve submits a bot review
anyway (below).
| Situation | Action |
|---|---|
the evaluation ends with approved |
one APPROVED review by the token on the current head (commit_id); none is written when it already exists. Body: Approved via /approve by <approvers> (OWNERS)., an explanation and the hidden marker <!-- prow-github-actions/approve-review -->. The approvers are named without @, so a fresh review after each push notifies nobody |
the evaluation ends without approved (/approve cancel, a CHANGES_REQUESTED review, files no longer covered, ...) |
every APPROVED review by the token that carries the marker is dismissed, on any commit, with approved removed: <reason>, ex: approved removed: no approver covers sdk/x.go; withdrawn by bob (/approve cancel) |
synchronize (a new head) while approved stays |
a fresh review on the new head; the one on the old commit is left as it is (GitHub may have dismissed it as stale). Reviews without the marker are never touched |
the PR's author is the token's own identity (a PR the bot opened, or a token secret of the author) |
GitHub does not let an author approve their own pull request: warning cannot submit the approval review: #<n> was opened by the token's own identity (<login>), and GitHub does not let an author approve their own pull request (approve.github_review), no review |
GitHub refuses with GitHub Actions is not permitted to approve pull requests. (reported as a 422 when "Allow GitHub Actions to create and approve pull requests" is off; GitHub does not document the status) |
warning, not failure: cannot submit the approval review: enable "Allow GitHub Actions to create and approve pull requests" (Settings → Actions → General) or pass a token that can (approve.github_review): <GitHub's message> |
GitHub refuses with any other 403 (ex: the workflow lacks pull-requests: write) |
warning, not failure: cannot submit the approval review: the token was refused; grant the workflow `pull-requests: write` (approve.github_review): <GitHub's message> |
| any other API error | could not submit the approval review: <error> or could not dismiss the approval review <id>: <error>, an error annotation; the run fails at the end, after the merge evaluation |
| the pull request is closed | nothing |
| the pull request is a draft | no review is submitted until it is marked ready for review (ready_for_review re-evaluates); dismissals still happen |
| GitHub refuses the dismissal (ex: "Restrict who can dismiss pull request reviews" excludes the token) | could not dismiss the approval review <id>: <error> on every evaluation of the pull request until the review is dismissed by hand |
The order inside one run is: compute the approval → write the label → edit the notifier →
submit or dismiss the review → the merge evaluation, so the merge evaluation of the same run starts
after the review exists (whether GitHub's mergeable_state already reflects it is another matter,
see required reviews).
The bot's review never approves. It is an output, never an input: a review carrying the
marker never counts as an approval, and with the setting on neither does any review by the
token's own user (when token is a user's PAT; GITHUB_TOKEN and GitHub App reviews are by a
Bot and never count anyway). Otherwise approved would hold itself up. To find that user the
bot reads GET /user once per run; an installation token is answered with 403 and needs no login.
Use a dedicated machine user, not a maintainer's PAT, or that maintainer's own reviews stop
counting. A pull_request_review event for the bot's review (only a user token fires one)
evaluates nothing in the approve plugin; the merge gate still runs, which is harmless.
Dismissing the bot's review by hand does not withdraw approval: the next evaluation submits it
again. Use /approve cancel. Turning the setting off leaves the reviews already submitted in
place; dismiss them by hand. Two runs evaluating the same pull request at the same moment (a push
and a review, say) may both submit a review; the duplicate is harmless.
With the setting off nothing changes: no GET /user, no review read beyond what
ignore_review_state already reads, no review written.
Zero behaviour change. /approve by an org member or collaborator makes the bot submit an
APPROVE review; /approve cancel and /remove-approve dismiss its latest one; no approved
label, no notifier. The same legacy path applies to /approve on an issue even when the
repository has OWNERS files (the root OWNERS file authorizes it), since an issue has no changed
files to cover.
The pull request plugins resolve the same set of OWNERS files per changed file. owners-label applies the union of their labels; blunderbuss draws reviewers from the union of their reviewers (and approvers), weighting each by how many changed files they cover. Prow's owners-label takes only the deepest OWNERS file's labels; here labels inherit from parent directories like approvers do, and options.no_parent_owners stops that inheritance too.
Authorization fails closed:
- a changed file with no covering
OWNERSfile fails the command with an error naming the file; - an
OWNERSfile that cannot be fetched or parsed (for exampleapproversis not a list) fails the command; - the org-member/collaborator fallback applies only when the repository has no
OWNERSfile at all.
The OWNERS file must be in YAML format. All entries are expected to be GitHub usernames (compared case-insensitively); teams are not supported.
| Key | Meaning |
|---|---|
approvers |
list of usernames whose /approve (or approving review, or authorship) covers the files under this directory; may also /lgtm on a pull request; reviewer candidates for blunderbuss unless exclude_approvers |
reviewers |
list of usernames who may use /lgtm; reviewer candidates for blunderbuss |
labels |
list of labels owners-label adds to a pull request touching this directory; applied verbatim, must exist in the repository |
options.no_parent_owners |
true stops inheritance from parent directories |
emeritus_approvers, emeritus_reviewers and filters are accepted but ignored (filters is noted in the debug log). OWNERS_ALIASES files and aliases are not supported. Unknown keys are tolerated. labels must be a list of strings; anything else fails like a malformed role.
# List of usernames who may use /lgtm and get review requests
reviewers:
- user1
- user2
- user3
# List of usernames who may use /approve
approvers:
- user1
- user2
- admin1
# Labels added to every pull request that touches this directory
labels:
- area/sdk
# Optional: do not inherit approvers, reviewers and labels from parent directories
options:
no_parent_owners: false