Repository automation¶
This repository uses one packaged JavaScript action for repository policy. The workflows use trusted code and policy from the default branch. They do not run code from a pull request head with a write token.
Supported foundation¶
The foundation provides these functions:
- Additive label synchronization from
.github/repo-automation/labels.yml. - Pull request metadata and reviewer reconciliation.
- Guarded
/lgtm,/approve,/hold,/unhold, and/retestcommands. - Review-change observation.
- A stable
repository-automation/merge-policycheck, guarded GitHub native SQUASH auto-merge enablement, and unsafe auto-merge disarm. - Conflict labels and metadata label repair for all open pull requests, including older requests and requests based on another feature branch.
- Labels-only policy evaluation for a merge controller outside this action.
The foundation does not install Prow or Tide. GitHub branch protection remains the final merge authority. The automation publishes its policy check, enables native SQUASH auto-merge for eligible pull requests, and disables unsafe native auto-merge requests. It does not call a direct merge endpoint.
Cherry-pick¶
/cherry-pick <branch> belongs to the Mokka agent, not to this action. The
action skips a line whose command name is exactly cherry-pick, followed by a
space, a tab, or the end of the line, whatever its arguments: it records no
command, reports no diagnostic, and makes no write for it. A review body with
that line is evaluated as if the line were absent. The parser's line checks run
first, so a /cherry-pick line longer than 4096 characters, or with a control
character other than tab, a format character, or a line or paragraph separator,
is still reported, and like any diagnostic it keeps a review /lgtm beside it
from counting.
The agent dispatches .github/workflows/cherrypick.yml from main with two
inputs: pr_number, the merged pull request, and target_branches, a
comma-separated list of release branches. The job runs only when
REPOSITORY_AUTOMATION_CHERRY_PICK_ENABLED is true. Dispatches share a
concurrency group only when they name the same pull request and the same branch
list, so a pending dispatch is never replaced by one for other branches.
For each branch, the workflow cherry-picks the merge commit GitHub recorded for
the pull request (the squash commit in this repository), recreates the result
as verified commits on the exact target commit it used, and opens or updates
the backport-<pr>-to-<branch> pull request. When the change is already on the
target branch, it comments that on the pull request, opens no backport pull
request, and counts the branch as done. It does not overwrite a backport branch
holding a commit the workflow did not create, such as a pushed conflict
resolution, an amended commit, or a web edit: that branch fails until its pull
request is merged or the branch is deleted. The workflow's own commits are the
verified ones, with author github-actions[bot] and committer GitHub, and the
unverified cherry-pick a stopped run pushed, so a retry after a failed run
replaces them. After every branch has been attempted and commented on, the run
fails if any branch failed.
/backport is not a command. The action reports it as unsupported, like any
other unknown command.
Assignment, review request, state, and title commands¶
/assign, /unassign, /cc, /uncc, /close, /reopen, and /retitle
also belong to the Mokka agent. The action skips them exactly as it skips
/cherry-pick: a line whose command name is one of these records no command,
reports no diagnostic, and makes no write, and a review body is evaluated as if
the line were absent. The parser's line checks still run first.
The agent reads these comments on issues and pull requests, applies Prow's
rules for who may use each command, and dispatches
.github/workflows/issue-commands.yml from main with these five string
inputs, always all five, with an empty string for the ones a command does not
use:
| Input | Value |
|---|---|
command |
assign, unassign, cc, uncc, close, reopen, or retitle |
number |
The issue or pull request number |
users |
Comma-separated GitHub logins without @, at most 10. Required for assign, unassign, cc, and uncc; refused for the other commands |
title |
The new title, for retitle only: at most 256 characters, no control or format characters, and no keyword that closes an issue, such as fixes #12 |
requester |
The commenter's login, shown in the run name |
The workflow decides nothing about who may use a command: anyone who can dispatch it already has write access. It checks the inputs, then makes the change with the calls Prow's GitHub client makes:
assignandunassignadd or remove assignees. GitHub silently skips a user it cannot assign, so the workflow compares the assignees GitHub returns.ccandunccrequest or remove pull request reviews. GitHub refuses a whole review request for one reviewer it cannot request, so the workflow then requests each reviewer alone. Both are refused on an issue.closecloses an issue as completed, or closes a pull request, and is refused on an item that is already closed.reopenreopens either, except a merged pull request, and does nothing on an item that is not closed.retitlesets the title.
After a successful close or reopen, the workflow posts the reply Prow's
lifecycle plugin posts, naming the requester, for example
@alice: Closing this issue. The other commands post nothing on success, as
in Prow.
When a command is refused, by one of the rules above or by GitHub with HTTP
403, 404, 410, or 422 or by leaving a user out, the workflow posts one comment
on the issue or pull request that names the command and the reason, and fails
the run. Inputs that fail validation get the same comment whenever number is
a positive integer: it names the rule, never repeats a title, and shows a
rejected login in a code span. An invalid number, or any other error, fails
the run without a comment.
The job runs only when REPOSITORY_AUTOMATION_ISSUE_COMMANDS_ENABLED is
true; until the agent dispatches the workflow, it does nothing. Each run is
its own concurrency group, because GitHub keeps one pending run per group and
a shared group would drop a dispatched command.
Activation order¶
Write-capable jobs are disabled when they first land. Enable a stage by setting
its repository variable to the exact lowercase string true. An unset variable
or any other value keeps the job disabled.
Activate the functions in this order:
- Run Label synchronization without
apply. Review the plan. Run it again withapplyonly after the plan is correct. - Set
REPOSITORY_AUTOMATION_METADATA_ENABLED=true. Confirm that PR metadata changes only managed metadata labels and reviewer requests. - Set
REPOSITORY_AUTOMATION_COMMANDS_ENABLED=true. Confirm command authorization and duplicate-delivery behavior on a test pull request. - Set
REPOSITORY_AUTOMATION_REVIEWS_ENABLED=true. Confirm that Merge evaluation receives only the expected completion events and keeps its job disabled. - Add
repository-automation/merge-policyas a required branch-protection check. Only then setREPOSITORY_AUTOMATION_MERGE_ENABLED=trueto publish policy checks, enable native SQUASH auto-merge for eligible pull requests, and disarm unsafe native auto-merge requests. This flag applies to all open requests selected by events, trusted dispatch completion, and the scheduled evaluator; it is not limited to a test PR. For strict SQUASH-only operation, repository settings must disable merge commits and rebase merges. The evaluator leaves an eligible SQUASH request armed and enables an eligible unarmed request. It disarms an unsafe method that it observes, but the method can change after its final read. If this flag is already enabled, installing this action also activates native enablement. - Set
REPOSITORY_AUTOMATION_CHERRY_PICK_ENABLED=trueonce the Mokka agent dispatches Cherry-Pick. Confirm the first backport pull request on a release branch. - Set
REPOSITORY_AUTOMATION_ISSUE_COMMANDS_ENABLED=trueonce the Mokka agent dispatches Issue commands. Confirm an/assignand a/retitleon a test issue or pull request.
Keep each earlier step active while you validate the next step. Do not enable a later write path when an earlier validation fails.
To hand the merge to another controller, follow the cutover in Labels-only policy evaluation.
Labels for all open pull requests¶
With REPOSITORY_AUTOMATION_METADATA_ENABLED=true, PR metadata scans all
open pull requests hourly, at minute 17. GitHub can delay scheduled runs. There is
no creation-date or update-date filter. A push to main or a release-* branch
also scans open requests based on that exact branch. The scheduled scan covers
every valid base branch in this repository, including stacked pull requests
and PRs created by bots.
The conflict scan adds needs-rebase only when GitHub reports CONFLICTING for
the current head and base tip. It removes that label only when GitHub reports
MERGEABLE. Unknown mergeability, a missing base, inconsistent identity, or a
changed head or base tip defers the request and preserves its labels.
PR branch updates (synchronize) also trigger this scan. The event's before
and after SHAs do not replace the live head, base tip, or mergeability checks.
The metadata scan repairs kind/*, size/*, area/*, and
do-not-merge/work-in-progress. It uses the same classifiers as PR metadata.
An invalid title preserves existing kind labels; size, area, and draft labels
can still be repaired. Invalid configuration preserves all existing labels.
The scan refreshes the trusted policy comment even when labels already match.
It validates configuration, title, DCO, and OWNERS before it emits current-head
metadata evidence. Failed validation removes that evidence and records the
diagnostics. It does not request reviewers. It preserves conflict,
approval, hold, and other labels outside its metadata ownership.
The refresh preserves valid command state, including a hold, from the same trusted bot comment. Duplicate comments, invalid state, or a changed comment stop the refresh. The API has no atomic compare-and-swap for comments. Commands and pull request metadata runs for the same pull request share one concurrency group, so they never rewrite its comment at the same time. Scans run in their own group, and only the final comment read limits their read-to-write race with a concurrent command.
Both scans fully read their candidate list before the first mutation and
reject a list above 100 requests instead of silently omitting requests. They
repeat live identity checks before each write. Metadata also checks the base
tip, derived labels, and the exact trusted policy revision. The job summary
records each request as applied, unchanged, deferred, or failed. Each scan also
logs the same bounded JSON with the prefix Repository automation conflict-labels:
or Repository automation metadata-labels:. These reports can be read through
the job-log API. A later API failure reports earlier writes; it does not claim
that they were rolled back. Review both reports and the current open list before
declaring a sweep complete. Investigate every deferred, failed, or missing request.
An initially correct metadata label set still needs fresh evidence and input checks. The scan verifies the stored comment and final labels, then repeats its live input checks. If a successful label update is still absent at the next read, the scan stops and reports the partial result instead of repeating the same update. The hourly schedule limits routine API use; the workflow token's actual rate limit is not assumed. A large first backfill can require another run after an API failure. Check the per-PR results before retrying.
Approval labels remain part of the guarded merge evaluator. They require its activation gates and current validated human review, command evidence, or applicable approver-author authority from trusted OWNERS. A metadata backfill does not directly grant approval or enable auto-merge.
Completion of a trusted PR metadata dispatch triggers the guarded merge evaluator for the bounded open pull request set. The evaluator reads current reviews, metadata, and source CI before it sets approval labels and the merge-policy check. With the merge flag enabled, an eligible request can also receive native SQUASH auto-merge.
The evaluator accepts only canonical v2 metadata evidence with an explicit successful validation result. It rejects legacy or ambiguous evidence and checks the live title and DCO against the checked-out policy. The exact trusted checkout revision must match the live default branch before and after each policy evaluation. A revision change blocks the merge policy and disarms native auto-merge on the same head when possible. The final read of a confirmed exact-head completed native merge needs no new policy evaluation and performs no further writes.
A trusted Review observer completion normally evaluates its mapped pull requests. If GitHub returns a valid empty PR mapping, the evaluator reads the current open PR list, limited to 100 candidates. It checks each candidate's current review and head before it changes approval labels or the merge-policy check. Invalid workflow identity or malformed mappings do not start this scan.
Native SQUASH auto-merge and source CI¶
The trusted evaluator job enables GitHub native auto-merge with SQUASH. Its
token has contents: write and pull-requests: write permissions for this
operation. It checks out only the trusted default-branch commit, with checkout
credentials disabled.
The evaluator reads the required source CI from merge.requiredCI in
.github/repo-automation/policy.yml. Each workflow entry names a workflow file
and, optionally, the changed-path globs that make it required; each check entry
names a check run and the GitHub App id that must publish it. The current list
requires successful current-head runs of Basic checks and
Validate changelog, plus a successful DCO check from the DCO app. It also
requires the action CI, Helm, dependency-integrity, and documentation workflows
when their tracked PR path filters match a changed or renamed path. It uses the
latest run number and current attempt for each required workflow. Runs must
belong to this repository, use the pull_request event, and match the current
head and expected workflow path. A populated PR mapping must identify this PR.
An empty mapping can identify the PR through the exact
@refs/pull/<number>/merge suffix. An empty mapping with a plain workflow path
can instead match the live source repository, source branch, and head SHA;
its normalized prNumber remains null. An explicit mapping to another PR
or source ref cannot use this fallback.
Missing or running evidence blocks success. Failed, cancelled, skipped, or
malformed required evidence also blocks success. Incomplete or over-limit API
collections fail closed. The merge-policy check and metadata/review workflows
do not satisfy the source CI gate.
The files patterns use a portable subset of glob syntax so that the agent can
evaluate the same list without a minimatch implementation. Configuration
validation rejects anything else, including braces, brackets, ?, extglob
groups, negation, and ** inside a segment. Each /-separated segment is
either ** or a run of letters, digits, ., _, and - in which * matches
any characters inside that segment, including a leading dot. ** matches zero
or more whole segments, except as the last segment, where it matches one or
more: docs/** matches docs/a.md but not docs, and **/OWNERS matches
OWNERS. Matching is case-sensitive and applies to each changed path and to the
previous path of a rename. Source CI can pass only for pull requests into
main or a release-* branch; any other base stays pending.
For an eligible request, the evaluator first publishes an action_required
policy check. It reads authority, metadata, PR identity, and CI again, then
enables native SQUASH auto-merge with expectedHeadOid when no request is armed.
It does not retry that mutation. It reads the same evidence again before it
publishes policy success and once more after success. Before success, failed
or revoked evidence keeps the check blocked. After success, the evaluator
attempts to restore a blocking check and disarm the request when it can confirm
the current PR and head identity. A base branch or source identity change for
the same head also triggers this repair. Failed reads or writes can prevent that
repair, and GitHub can already have completed the merge. An existing eligible
SQUASH request is preserved.
These reads and the success check are separate GitHub operations. Evidence can
change between them. The head guard does not pin reviews, labels, CI, or the
base revision. CI evidence is tied to the source head and can be reused across
PRs or base retargets when GitHub omits PR mappings. It does not prove that the
current base tip or a retargeted base branch was tested. With branch protection
strict=false, GitHub can merge without an up-to-date base. Required native
reviews and checks remain the final merge controls. A source CI gate in this
action does not make a separately required native CI check redundant.
Labels-only policy evaluation¶
The Merge evaluation workflow has a second job, policy-labels, gated by
REPOSITORY_AUTOMATION_POLICY_LABELS_ENABLED. It runs on the same events as the
merge job and reads the same review, command, and approver-author authority from
the trusted default branch. It writes only the lgtm, approved,
do-not-merge/hold, and do-not-merge/needs-approval labels. It does not read
source CI, branch protection, or merge state, never publishes the merge-policy
check, and never enables or disables native auto-merge. Its token has
actions: read, contents: read, issues: write, and pull-requests: write,
and the job has its own concurrency group.
Before it writes, the job reads the policy comment, the labels, and the pull
request head again. If any of them changed since its evaluation read, it skips
the write and reports inputs-changed; the run that made the change triggers the
next evaluation. If the evaluation fails, the job writes nothing and the run
fails. Unlike the merge job, it does not apply a fail-closed label plan, because
that plan would remove an active do-not-merge/hold.
The job never removes do-not-merge/hold. Only an /unhold clears a hold, and
the Commands run that applies it removes the label. A hold label without hold
state stays, for example one that a maintainer added by hand or one that a
/hold run wrote before its state.
Use this job when another controller merges on these labels. Follow this order, because the merge job enables native auto-merge again on eligible pull requests for as long as it is enabled:
- Set
REPOSITORY_AUTOMATION_POLICY_LABELS_ENABLED=trueand confirm that a Merge evaluation run applies labels from itspolicy-labelsjob. - Set
REPOSITORY_AUTOMATION_MERGE_ENABLED=false. Wait until no Merge evaluationevaluatejob is queued or running. - Disable native auto-merge on every open pull request that has it, into
mainand into everyrelease-*branch. This command lists them:
gh pr list --repo NVIDIA/k8s-test-infra --state open --limit 200 \
--json number,baseRefName,autoMergeRequest \
--jq '.[] | select(.autoMergeRequest != null) | "\(.number) \(.baseRefName)"'
Run gh pr merge <number> --repo NVIDIA/k8s-test-infra --disable-auto for
each listed pull request. Repeat the list command until it prints nothing.
4. In branch protection for main and for every release-* rule, replace the
required repository-automation/merge-policy check with the gate of the new
controller.
While both variables are true, both jobs write the same labels from separate
concurrency groups. Keep that overlap short.
Automatic approval for approver authors¶
A PR author who is a verified human approver in trusted base OWNERS implicitly
approves the changed files within that approver's authority. The evaluator adds
approved when those files and any independent approvals cover every changed
file. OWNERS aliases and active nested OWNERS rules apply; no_parent_owners
can exclude a root approver. The PR's proposed OWNERS changes cannot grant this
authority. Metadata also accepts those author-owned paths without requesting
a review from the author.
An independent authorized human must still provide /lgtm for the current
head. The author cannot give their own PR an LGTM. The evaluator reads author
identity and trusted OWNERS again before success; removed authority revokes
implicit approval. A new head invalidates old LGTM evidence and requires a
fresh coverage check. Holds, required checks, and GitHub's native review
requirements still apply. An approved label does not satisfy a required
GitHub approval review or enable auto-merge.
Conversation commands use the same current native approval and review LGTM
evidence as merge evaluation. Approval coverage can combine native reviews,
/approve commands, and approver-author authority across separate OWNERS
scopes. A /hold command preserves valid approval and LGTM labels. Commands
read trusted OWNERS and validated review evidence again before any write;
changed evidence stops the run. Native reviews remain live evidence and are
not copied into command state.
Queued commands¶
GitHub keeps at most one pending run in a concurrency group, so a newer
Commands or PR metadata run for the same pull request cancels a pending
Commands run. Each Commands run therefore lists the pull request comments
and first applies the /hold, /unhold, and /retest commands from unprocessed
comments that are older than its own comment, in comment ID order, then its own.
A run started by a comment without commands also does this. Each comment must
come from a human account and must not be edited, and its author's live
identity and repository access are checked as for the comment that started the
run. A caught-up comment
is recorded as processed only when it changed something, or when it had an
/lgtm or /approve rejected that its author is allowed to give. Comments from
other commenters that change nothing therefore cannot fill the command history.
Processed comment IDs are stored in the policy comment, so a repeated delivery
changes nothing.
Only comments created in the last 24 hours, by GitHub's comment creation time,
are caught up, so a first run does not replay old history. Comments older than
the newest processed command are not applied later, because that would reorder
them after newer commands. Comments newer than the run's own comment are left to
their own run. A caught-up /lgtm or /approve from a reviewer or approver who
may give it is recorded as processed but grants no evidence, because it may have
been written before a push to the head; the reviewer must comment again. A
caught-up /lgtm or /approve from anyone else, including the pull request
author, is ignored and not shown. The command summary in the
policy comment names each such comment ID and asks for a re-issue on the
current head, for up to 20 comments plus a count of the rest. It also shows the
line results of the last processed comment, and the job summary lists every
processed comment ID. A comment whose run was cancelled stays unapplied until the next
Commands run for that pull request, and is dropped if that run starts more
than 24 hours later.
Dispatched label scan reports¶
The PR metadata workflow also accepts a workflow_dispatch request on
main, with exactly two string inputs: request_id (a canonical lowercase
UUID) and workflow_commit_sha (the full lowercase main commit SHA). The
metadata flag must be enabled. The selected workflow commit, run commit,
and input commit must match in NVIDIA/k8s-test-infra. The workflow checks
out that exact trusted commit in control; it accepts no checkout path or
other caller input.
Dispatch runs conflict-labels and metadata-labels as two separate action
calls. The metadata scan runs even if the conflict scan fails. After both
calls, the workflow uploads mokka-label-scan-<request_id> with the fixed
members conflict-labels.json and metadata-labels.json, then fails the job
if either scan failed. A failure before a complete candidate list is known
cannot produce a report that claims an empty list.
Each version 1 report contains the request and repository identities, the
workflow commit, mode, dry-run state, complete sorted candidate numbers, and
one result for each candidate. Results use applied, unchanged, deferred,
or failed, with a fixed reason and SHA-256 hashes of the input fence and
managed labels. An unprocessed request is failed with not_processed.
Reports contain no raw titles, node IDs, branch names, or label names. Each
report is limited to 70 KiB and 100 candidates.
Dispatch success requires a fresh label read followed by a final PR fence, including requests with initially correct labels. An acknowledged update that is still absent at the next read fails the scan. Unknown mergeability, changed state, or an invalid policy leaves coverage unresolved and has no output label hash. The complete reports can thus be checked against a later live observation. Native PR, push, and scheduled runs keep their existing summary and behavior; they do not create these report files. Keep the hourly recovery schedule until the poll-driven dispatch path has passed its live activation checks.
LGTM in pull request reviews¶
The merge evaluator accepts an explicit /lgtm line in the current body of an
APPROVED or COMMENTED review. The reviewer must be a verified human, a current
OWNERS reviewer or approver for the changed files, and not the pull request
author. The review must refer to the current pull request head. Human reviews
can grant evidence on a pull request authored by a bot.
Review LGTM is separate from native approval coverage. An approving review
without /lgtm does not grant LGTM. A COMMENTED review with /lgtm does not
grant approval. Other commands in review bodies are not executed. Quoted or
fenced commands do not count, and invalid command syntax does not grant review
LGTM. /lgtm cancel is not a supported command.
The latest submitted review from each actor replaces that actor's older review
LGTM. Submission time determines the order; the higher review ID breaks a tie.
A pending review does not replace submitted evidence. The evaluator reads the
current review body, state, author, commit, and submission time again after
label changes, before the success check, and after that check. Removing or
replacing /lgtm, dismissing the review, or changing the pull request head
removes that review evidence. Review commands are not stored as historical
authority in the policy comment. Stored
issue-comment LGTM remains separate and must pass its existing live checks.
Bot-authored pull requests can receive metadata labels and human reviewer requests. Bot reviews are validated and then excluded from human approval evidence. A bot cannot provide OWNERS, reviewer, approver, or LGTM authority.
Current metadata evidence does not require an earlier conversation command. A trusted metadata comment can have no command-state record. Unknown, malformed, duplicate, or wrong-context command-state records remain blocked.
Security and operations¶
- Keep top-level workflow permissions empty. Grant permissions per job.
- Keep every external action pinned to a complete commit SHA.
- Keep the automation checkout separate from the target checkout.
- Resolve the default branch through GitHub and check out its exact commit SHA.
- Do not enable credentials, submodules, or LFS in the automation checkout.
- Treat comments and event payload text as hints. The action refetches live GitHub state before it writes.
- Review the action job summary for each invocation.
- Keep the scheduled evaluator because it repairs missed or delayed events.
To stop repository writes, set the applicable activation variable to false.
After merge evaluation is disabled, maintainers must disable native auto-merge
on every open pull request and confirm that none is still armed, because the
evaluator enables it again while it runs. Do not remove the required merge check
until maintainers select and document a replacement gate.