Each CODEOWNERS rule names a workable number of owners
github/codeowners-owners-per-rule@v1
Every rule in CODEOWNERS names at least the minimum and at most the maximum number of owners, so review neither depends on one person nor becomes everybody's problem.
| Id | github/codeowners-owners-per-rule |
| Version | v1 |
| Category | github |
| Default severity | warning |
| Interpreter | python3 |
| Timeout | 30 seconds |
| Violations tolerated | 0 |
| Collects | github |
Collectors
This guardrail doesn't gather anything itself. It relies on the collectors below, which the CLI runs once per build before any check, and reads what they found from GUARDRAIL_FACTS. If a collector collects nothing, this guardrail is skipped, not failed.
| Collector | Gathers | Inputs it is given |
|---|---|---|
github | What GitHub itself reads out of the repository: the Actions workflows in .github/workflows, read as they are written, and the CODEOWNERS file, read as rules. For a workflow, which events start it, what token permissions it hands a job, which runner each job asks for, and every action a step reaches for and how tightly it is pinned. For CODEOWNERS, every pattern in file order with the owners it names, and the owners that apply to the paths a guardrail asks about, resolved the way GitHub resolves them, where the last rule to match a path is the one that owns it. Names and shapes only, never a secret, an input value or an environment value. | workflows |
The inputs above are this guardrail's own inputs, passed straight through to the collector. Setting one in buildnote.json changes what is collected, and two guardrails configured the same way share a single collection.
Configuration
{
"guardrails": {
"failOn": "error",
"checks": [
{
"use": "github/codeowners-owners-per-rule@v1",
"severity": "warning",
"with": {
"workflows": ".github/workflows/*.yml,.github/workflows/*.yaml",
"minOwners": "1",
"maxOwners": "5"
},
"exemptions": []
}
]
}
}Inputs
| Input | Description | Default | Environment variable |
|---|---|---|---|
workflows | Comma separated globs naming the workflows to read. | .github/workflows/*.yml,.github/workflows/*.yaml | GUARDRAIL_INPUT_WORKFLOWS |
minOwners | Fewest owners a rule may name. A rule naming none is left to github/codeowners-no-unowned-rules. | 1 | GUARDRAIL_INPUT_MINOWNERS |
maxOwners | Most owners a rule may name. 0 means no limit. | 5 | GUARDRAIL_INPUT_MAXOWNERS |
How to fix
Adjust the rules listed in the violations. A single owner is a single point of failure: when they are on leave, nobody can review the path. A long list fails the other way, because GitHub requests a review from all of them and each one assumes someone else will pick it up.
A GitHub team counts as one owner:
/api/ @company/backendThis is usually the right answer, because team membership is then managed in the team instead of in this file.
More in github
github/actions-pinned-by-digest. Every third-party action a workflow uses is pinned to a full commit sha, not to a tag or a branch.github/codeowners-catch-all.CODEOWNERShas a rule matching every path, so even a file nobody thought about has an owner.github/codeowners-no-unowned-rules. No rule inCODEOWNERShas an empty list of owners, which would remove ownership from everything it matches.github/codeowners-parses. Every line ofCODEOWNERSthat is not a comment is a rule GitHub can read, so no ownership is silently lost to a line GitHub ignores.github/codeowners-present. The repository has aCODEOWNERSfile where GitHub looks for one, with at least one rule, so every change has someone to review it.github/codeowners-team-owned. Every rule inCODEOWNERSnames at least one GitHub team, so ownership outlasts the people currently on it.github/job-timeout-set. Every job declares how long it may run, so a hung job is stopped instead of holding a runner until GitHub's own limit.github/least-privilege-token. Every workflow declarespermissions, and none of them grants write access to everything.