Prow github actions reads one YAML document, prow.yaml, from up to two places and
layers them: an organization-wide file, then the repository's own file. A repository
with only the legacy .prowlabels.yaml needs no changes.
Tiers, lowest precedence first. Later tiers override earlier ones.
| Tier | Location | Notes |
|---|---|---|
| Built-in | /lifecycle, /stage, /status defaults |
see labeling |
| Organization | <owner>/.project repo, prow.yaml; then <owner>/.github repo, prow.yaml |
first found wins; a missing repo or file is skipped |
| Explicit source | the config input |
replaces the organization tier when set; must exist |
| Repository | the event repository, default branch: .github/prow.yaml, .github/prowlabels.yaml, prow.yaml, .prowlabels.yaml, then the .yml spelling of each, in that order |
first found wins |
A 404 on an organization or repository probe is silent. Any other error (permissions,
rate limit, 5xx) fails the run. Label commands require a file in at least one tier;
otherwise the run fails with
no prow configuration found: looked for prow.yaml in <owner>/.project and <owner>/.github, and .github/prow.yaml, .github/prowlabels.yaml, prow.yaml, .prowlabels.yaml (.yaml/.yml) in <owner>/<repo>.
The configuration is read once per run, however many commands the comment carries.
- uses: cncf/prow-github-actions@v3.0.1
with:
config: cncf/prow-config:configs/prow.yaml@v1
github-token: ${{ secrets.GITHUB_TOKEN }}| Form | Read with | Notes |
|---|---|---|
owner/repo:path[@ref] |
github-token |
ref is a branch, tag or SHA; defaults to the default branch |
https://… |
anonymous fetch, 5 s timeout |
must answer 2xx; http:// is refused |
When config is set the <owner>/.project and <owner>/.github repositories are not
consulted. The repository tier still applies on top. A 404 or non-2xx answer fails the run:
an explicit source must exist.
A private cross-repository source is readable only when github-token can read it. The
default GITHUB_TOKEN is scoped to the event repository, so use a token (or GitHub App
installation token) with read access to the configuration repository. There is no
separate token input.
labels:
# plain list: labels stack
area:
- api
- docs
# values may carry the GitHub label color (6 hex digits) and description
kind:
- name: bug
color: d73a4a
description: Something is not working
- name: cleanup
color: c5def5
- feature
# mapping: a later /priority replaces any existing priority/* label
priority:
values: [low, high]
exclusive: true
# the /label allowlist, applied verbatim without a prefix
labels:
- documentation
- question
# needs-* labels: see the require_matching_label section below
require_matching_label:
- regexp: ^kind/
missing_label: needs-kind
issues: true
prs: true
missing_comment: Please add a kind label with /kind.
- regexp: ^area/
missing_label: needs-area
prs: true
grace_period_duration: 5s
# the merge gate; these are the defaults (labels is [lgtm, approved] once the pull request's base branch has OWNERS files)
tide:
labels: [lgtm]
missing_labels: [do-not-merge/*, needs-rebase, hold]
merge_method: merge
merge_on_events: true
merge_queue: auto
# the label /hold applies; this is the default
hold:
label: do-not-merge/hold
# reviewers requested from OWNERS files when a pull request opens
blunderbuss:
request_count: 2
max_request_count: 4
exclude_approvers: false
ignore_drafts: true
ignore_authors: ['dependabot[bot]']
# /approve on repositories with OWNERS files; these are the defaults
approve:
require_self_approval: false
ignore_review_state: false
lgtm_acts_as_approve: false
github_review: false
# /lgtm records the reviewed commit as a prow/lgtm commit status; this is the default
lgtm:
bind_to_commit: true
# the sweep job evaluates the pull requests updated within this window; this is the default
sweep:
lookback: 1h
# who may run the label, /hold, /close and review commands; these are the defaults
authorization:
labels: anyone
hold: anyone
close: collaborators
review: members
users: []A mapping of label sections. Every key becomes a /<key> command once listed in
prow-commands; see labeling for command semantics.
| Section form | Meaning |
|---|---|
key: [a, b] |
allowed values; labels stack |
key: { values: [a, b], exclusive: bool } |
exclusive: true replaces existing key/* labels |
| Value form | Meaning |
|---|---|
bug |
the label key/bug |
{ name: bug, color: d73a4a, description: … } |
the same label with metadata; color is six hex digits, no # |
color and description are applied by the label-sync job;
label commands themselves only use name. Quote a color made only of digits
(color: '123456') so YAML keeps it a string.
The /label allowlist is the section named labels inside labels:
labels:
labels:
- documentationThe label-sync job creates and updates the union of:
| Source | Labels | Color, description |
|---|---|---|
every labels.<key> section |
<key>/<value>; the /label allowlist labels.labels verbatim |
from the value's color and description |
built-in /lifecycle, /stage, /status values |
lifecycle/frozen, lifecycle/stale, lifecycle/rotten, stage/alpha, stage/beta, stage/stable, status/approved-for-milestone, status/in-progress, status/in-review |
only when the configuration has no section of that key |
| the action's own commands | lgtm, approved, hold.label (do-not-merge/hold) and the legacy hold, help wanted, good first issue |
built-in |
require_matching_label |
every missing_label |
ededed for needs-* |
Precedence per label: the configuration's color/description, then the built-in
default, otherwise none (GitHub picks a color; no description is written). Built-in
colors follow kubernetes/test-infra label_sync
where the label exists there:
| Label | Color |
|---|---|
lgtm |
15dd18 |
approved |
0ffa16 |
hold, do-not-merge/hold |
e11d21 |
help wanted |
006b75 |
good first issue |
7057ff |
lifecycle/frozen |
d3e2f0 |
lifecycle/stale |
795548 |
lifecycle/rotten |
604460 |
needs-* |
ededed |
Labels declared under labels: in OWNERS files are not part of
the catalogue: the job has no pull request to scope a tree walk to. List them in a
labels section (or the /label allowlist) as well, or create them by hand;
owners-label skips a label the repository lacks.
Names are unique case-insensitively (the first definition wins) and the job never deletes or renames a label. Label commands refuse labels the repository does not have (labeling), so run the job after changing the configuration:
name: Sync labels from prow.yaml
on:
workflow_dispatch:
push:
branches: [main]
paths: [.github/prow.yaml]
permissions:
contents: read
issues: write
jobs:
execute:
runs-on: ubuntu-latest
steps:
- uses: cncf/prow-github-actions@v3.0.1
with:
jobs: label-sync
github-token: '${{ secrets.GITHUB_TOKEN }}'A list of rules modelled on Prow's require-matching-label
plugin: an issue or pull request with no label matching regexp gets missing_label;
once a matching label arrives missing_label is removed again. Each missing_label is
part of the label catalogue, so the
label-sync job creates it; a missing_label the repository
does not have fails the run like any other label
(labeling).
| Field | Required | Meaning |
|---|---|---|
regexp |
yes | a JavaScript regular expression matched, case-sensitively, against every label on the object |
missing_label |
yes | the label added when nothing matches; compared case-insensitively |
issues, prs |
no | which objects the rule applies to; when neither is set both are true, when one is set the other is false |
missing_comment |
no | comment posted together with missing_label; deleted again when the label is removed |
grace_period_duration |
no | how long opened/reopened wait before evaluating, ex: 5s, 2m, 500ms; default 0, capped at 30s |
| Event | Evaluates |
|---|---|
issues / pull_request opened, reopened |
every applicable rule, after the longest grace_period_duration among them (so other labelers act first); labels are re-read after the wait |
issues / pull_request labeled, unlabeled |
the rules whose regexp matches the changed label, or whose missing_label is the changed label |
issue_comment /check-required-labels |
every applicable rule, no grace period |
| Labels on the object | Action |
|---|---|
no regexp match, no missing_label |
add missing_label; post missing_comment if set and not already posted |
a regexp match and missing_label |
remove missing_label; delete the bot's earlier missing_comment |
| otherwise | nothing |
The comment ends with an invisible marker,
<!-- prow-github-actions/require-matching-label: <missing_label> -->, which is how a later
run finds the bot's own comment to delete it and avoids posting it twice. Only comments by a
bot account (github-actions[bot] or any Bot user) are deleted.
A grace_period_duration in Go's syntax (1m30s) is accepted; anything else fails the run.
Longer than 30s is clamped because the wait burns Actions minutes.
Divergences from Prow:
- a
labeled/unlabeledevent on themissing_labelitself re-evaluates the rule, so aneeds-kindremoved by hand while nokind/*label exists is re-added. Prow only reacts to labels matchingregexp. This matches the cncf/automation labeler that repositories are migrating from; - the grace period is capped at
30s.
The workflow must subscribe to the events; see events.
The merge gate of the event handlers and the lgtm
job, modelled on Prow's tide query. Every field is optional; a configured list replaces the
default list rather than extending it.
| Field | Default | Meaning |
|---|---|---|
labels |
[lgtm]; [lgtm, approved] when the PR's base branch has OWNERS files |
every pattern must match a label on the PR |
missing_labels |
[do-not-merge/*, needs-rebase, hold] |
no pattern may match a label on the PR |
merge_method |
the merge-method input, else merge |
merge, squash or rebase; wins over the input |
merge_on_events |
true |
false: the pull_request, pull_request_review and check_suite handlers merge nothing; only the cron does (event-driven merging) |
merge_queue |
auto |
auto: when the base branch requires a GitHub merge queue, a PR that passes the gate is enqueued (one GraphQL query, then enqueuePullRequest) instead of merged, and the bot's entry is dequeued when the gate breaks; off: never call GraphQL, always PUT /merge (a 405 on a queue branch) (merge queues) |
Entries are label names compared case-insensitively; * matches any run of characters,
/ included. An empty name, an unknown merge_method, a non-boolean merge_on_events or a
merge_queue other than auto/off fails the run.
The labels default is decided per pull request from its base branch: when tide.labels is not
configured, one recursive listing of that branch's tree (GET /git/trees/{base}, memoized per
branch for the run) tells whether any OWNERS file exists; if so
approved joins lgtm. The OWNERS that
/approve consults come from the same branch. An explicit labels list always wins and skips
the listing.
| Field | Default | Meaning |
|---|---|---|
label |
do-not-merge/hold |
the label /hold applies; cancel removes it and the legacy hold |
The label is in the label catalogue and, like every command label,
must exist in the repository. It is refused by /label and /remove-label. Set
label: hold to keep the pre-do-not-merge/hold name; see
upgrading.
Modelled on Prow's blunderbuss
plugin: when a pull request opens, request reviews from the people the
OWNERS files of the base branch name for its changed files. The
section is optional; with no OWNERS files in the repository the plugin is a no-op.
| Field | Default | Meaning |
|---|---|---|
request_count |
2 |
reviewers to request; integer ≥ 1 |
max_request_count |
unset | never leave the pull request with more requested reviewers than this in total; ≥ request_count |
exclude_approvers |
false |
only reviewers are candidates, not approvers |
ignore_drafts |
true |
a draft waits for ready_for_review; false requests on opened and skips ready_for_review |
ignore_authors |
[] |
pull requests by these authors get no automatic request (case-insensitive) |
| Event | Action |
|---|---|
pull_request opened |
request request_count reviewers, unless the PR is a draft and ignore_drafts is on, or the author is in ignore_authors |
pull_request ready_for_review |
the same, only while ignore_drafts is on |
issue_comment /auto-cc |
the same, on any open pull request, ignoring ignore_drafts and ignore_authors |
Candidates are the union of reviewers (plus approvers unless exclude_approvers) of
every OWNERS file covering a changed file, minus the author, the already requested
reviewers and the assignees. Like Prow, each candidate is weighted by the number of changed
files they cover: the request is filled from the best-covering candidates first and drawn at
random among equals. With max_request_count, request_count is reduced so that the
already requested reviewers plus the new ones do not exceed it. Nobody left to request is a
debug-logged no-op; a refused request fails the run. No comment is posted.
The workflow token needs pull-requests: write and the workflow must subscribe to
ready_for_review for drafts; see events.
Settings of the /approve plugin, which only acts on repositories
with OWNERS files. Every field is a boolean; anything else fails the run.
| Field | Default | Meaning |
|---|---|---|
require_self_approval |
false |
false: the PR author implicitly approves every changed file their OWNERS entries cover (Prow's default). true: the author never counts, implicitly or through /approve |
ignore_review_state |
false |
true: GitHub reviews neither add (APPROVED) nor remove (CHANGES_REQUESTED) approvers |
lgtm_acts_as_approve |
false |
true: /lgtm counts as /approve and /lgtm cancel as /approve cancel when computing approval; the lgtm label is unaffected |
github_review |
false |
true: while the PR carries approved, the token keeps an APPROVE review on the PR's head commit, and dismisses it when approved goes away, so that a required approving review is met by the Prow decision (mirrored review). Needs "Allow GitHub Actions to create and approve pull requests" with GITHUB_TOKEN; that setting is repo-wide and lets any workflow approve PRs with GITHUB_TOKEN, so a dedicated GitHub App or machine-user token is the safer setup (repository setting). false: no review is written and no extra API call is made |
| Field | Default | Meaning |
|---|---|---|
bind_to_commit |
true |
/lgtm (and a hand-applied lgtm label under pull_request/pull_request_target) records the PR's head commit as a prow/lgtm commit status, and every merge path merges only while the head still carries it; a stale lgtm is removed with a comment. Needs statuses: write. false: label-only semantics, no status is written or read (automatic merging) |
| Field | Default | Meaning |
|---|---|---|
lookback |
1h |
how far back a pull request's updated_at may be for the sweep job to evaluate it; a Go style duration (30m, 2h), longer than 0, capped at 24h |
Who may run the label commands, /hold, /close and the review commands. Every field is
optional and the defaults are the gates the commands always had, so a repository without this
section behaves as before. Like the rest of the file it is read from the default branch, so a
pull request cannot add its own author to users, unless the config input
points at a @ref or an https URL (chosen by the workflow author).
| Field | Default | Meaning |
|---|---|---|
labels |
anyone |
the label commands: /area, /kind, /priority, /label, /lifecycle, /stage, /status, /help, /good-first-issue, every dynamic /<key> (/triage) and all their /remove- forms |
hold |
anyone |
/hold, /hold cancel, /unhold, /remove-hold |
close |
collaborators |
/close, /close not-planned, /reopen; the issue or PR author is always allowed |
review |
members |
/lgtm, /approve, /retest, /test, /ok-to-test and their cancel forms, on repositories without OWNERS files only (for issues: without a root OWNERS file); members or trusted |
users |
[] |
further trusted GitHub logins, compared case-insensitively |
| Who passes | Policy | Accepted by |
|---|---|---|
| everybody, with no API call | anyone |
labels, hold, close |
| repository collaborators | collaborators |
labels, hold, close |
| org members and collaborators | members |
labels, hold, close, review |
users, org members, collaborators, and the reviewers and approvers of the root OWNERS file of the default branch |
trusted |
labels, hold, close, review |
Checks run cheapest first and stop at the first that passes: users (no API call), org
membership, the collaborator check, then the root OWNERS file. /close and /reopen keep
their order (author, then collaborator) and read this section only when both refuse; the review
commands keep theirs (org member and collaborator) and read it only when both refuse. Any other
value fails the run; review accepts neither anyone nor collaborators, so this section never
widens who may merge beyond members, collaborators and users.
review only applies where the org-member/collaborator fallback does, on a repository without
any OWNERS file. Where OWNERS files exist they alone decide /lgtm,
/approve and the trigger commands, users grants nothing there, and review: trusted is a
no-op. Without OWNERS files the OWNERS part of trusted is moot, so review: trusted admits
users, org members and collaborators.
users is the one key that does not follow the repository-over-organization rule: the
organization and repository lists are unioned (duplicates dropped, case-insensitively), so a
repository can add its own triagers without copying the organization's. The organization tier
supplies defaults, not floors: a repository can set any key looser than the organization does,
and users can only grow. Organizations that need enforcement should use GitHub rulesets
(code-owner review, required teams, disallowing GitHub Actions approvals).
A refused label command or /hold fails the run with
<user> is not authorized to run <command>: authorization.<key> is <policy>, writes no label and
posts no comment; a refused /close or /reopen silently does nothing, even when the
configuration cannot be loaded (a warning is logged), while the label commands and /hold fail
the run when it cannot be loaded; a refused review command replies and fails the run as it always
did, with <user> is not a org member, collaborator or listed in authorization.users under
review: trusted (not authorized). When
the configuration cannot be loaded on that path, a warning is logged and the refusal falls back to
the members message, so the configuration error never reaches the public comment.
Warning
Do not add an OWNERS file just to grant triage through trusted. Once a repository has any
OWNERS file, /lgtm and /approve are authorized by OWNERS alone (org members it does not list
lose them), the tide.labels default becomes [lgtm, approved],
blunderbuss requests reviews from the listed people and the
approve plugin posts [APPROVALNOTIFIER] comments. Use users instead.
When labels is not anyone, a require_matching_label
missing_comment should not tell authors to run label commands such as /kind they may no longer
run.
No configuration. Whenever a pull request is opened, reopened or synchronized the
labels: of the OWNERS files covering its changed files are added; see
labeling. The labels must already exist in the
repository — declare them in a labels section for the label-sync job
or create them by hand.
Unknown top level keys are ignored and logged once at debug level.
The pre-existing format is a flat map of label sections and is still accepted at every
path, including prow.yaml:
area:
- bug
labels:
- documentation
triage:
values: [accepted, needs-information]
exclusive: trueBoth forms share one parser. The top level labels key decides which form a document is:
labels is |
Form | The /label allowlist is |
|---|---|---|
| a list | legacy: every top level key is a label section | the top level labels list |
| a mapping | new: require_matching_label, tide, hold, blunderbuss, approve, lgtm, sweep, authorization may sit alongside |
labels.labels |
absent, and require_matching_label, tide, hold, blunderbuss, approve, lgtm, sweep or authorization is present |
new | labels.labels |
| absent otherwise | legacy | none |
# legacy: /label may apply 'documentation'
labels:
- documentation
kind:
- bug# new: the same allowlist, nested
labels:
labels:
- documentation
kind:
- bugIn a legacy document every key must be a valid label section; anything else fails with
<key>: yaml malformed, expected a list of values or { values: [...], exclusive: bool }.
Organization (or explicit source) first, repository on top.
| Key | Rule |
|---|---|
labels |
per section: a repository section replaces the organization section of the same name; other organization sections survive |
require_matching_label |
lists concatenate, organization rules first |
tide, hold, blunderbuss, approve, lgtm, sweep, authorization |
shallow merge; a repository field wins |
authorization.users |
lists union, organization logins first, duplicates dropped case-insensitively |
# <owner>/.project prow.yaml
labels:
kind: [bug, cleanup]
area: [api]# <repo> .github/prow.yaml
labels:
kind: [docs]The repository sees kind: [docs] and area: [api].