Code owners
The CODEOWNERS file the repository carries, read as rules rather than as text: every pattern in file order with the owners it names, how many rules each owner is named by, 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.
| Facts key | codeowners |
| Version | v1 |
| Script | codeowners.py |
| Timeout | 60 seconds |
Inputs
| Input | Description | Default | Environment |
|---|---|---|---|
paths | Comma separated paths to resolve owners for, relative to the repository root. A guardrail that cares about the files a pull request changed names them here. | README.md,LICENSE | GUARDRAIL_INPUT_PATHS |
A guardrail that declares an input of the same name passes it through, so the guardrail's configuration in buildnote.json is what decides these values.
A guardrail asks for these facts by name, and reads them back the same way:
{
"collect": ["codeowners"]
}codeowners = guardrail.facts("codeowners")Facts
The document codeowners collects. A path carrying [] is an entry of the list before it, and one carrying [path] is a key of the object before it.
| Fact | Meaning |
|---|---|
path | Path of the CODEOWNERS file that was read: the first of CODEOWNERS, .github/CODEOWNERS and docs/CODEOWNERS that exists, the three locations GitHub reads a CODEOWNERS file from. |
rules | Every rule the file declares, in file order, which is the order they are resolved in. |
rules[].pattern | Pattern exactly as it is written, so an anchored /docs/ reads differently from a docs/ matched at any depth. |
rules[].owners | Owners the rule names, as users (@dana), teams (@acme/platform) or email addresses. Empty when the rule names none, which removes ownership from everything it matches. |
rules[].line | Line the rule is declared on, so a verdict points at the file. |
owners | Every owner the file names, by owner, with how many rules name them. |
matched | Owners resolved for each path the paths input named, by the path exactly as it was given. A path nobody asked about is absent, so read the ones the guardrail configured rather than iterating. |
matched[path].owners | Owners the last matching rule names. Empty when no rule matched the path, and empty when the last one to match names no owners. |
matched[path].rule | Pattern of the last rule that matched, or null when none did. |
matched[path].line | Line that rule is declared on, or null when none matched. |
unparsed | Every line that is not a rule, in file order. Comments and blank lines are not among them, because they are ignored rather than misread. |
unparsed[].line | Line that could not be read as a rule. |
unparsed[].reason | Why it could not be read as one. |
source | report when a CODEOWNERS file was read, none when the repository carries none. |
reason | Why nothing was read, present only when source is none. |
reports | The CODEOWNERS file that was read, as the one report this collector reads. |
reports[].path | Path of the file, relative to the directory the CLI runs in. |
reports[].modified | When the file was last written, ISO 8601. |
reports[].format | Format it was read as, always codeowners. |
A collector that cannot finish prints what it had along with an incomplete key saying why, so the facts below the point it stopped are absent. A check that cares reads incomplete before the rest.
Example facts
The example facts collected by codeowners collector:
{
"path": "CODEOWNERS",
"rules": [
{
"pattern": "*",
"owners": [
"@acme/platform"
],
"line": 2
},
{
"pattern": "*.md",
"owners": [
"@acme/docs",
"docs@acme.example"
],
"line": 5
},
{
"pattern": "/service/",
"owners": [
"@acme/service-team"
],
"line": 8
},
{
"pattern": "/service/build/",
"owners": [],
"line": 11
}
],
"owners": {
"@acme/platform": 1,
"@acme/docs": 1,
"docs@acme.example": 1,
"@acme/service-team": 1
},
"matched": {
"README.md": {
"owners": [
"@acme/docs",
"docs@acme.example"
],
"rule": "*.md",
"line": 5
},
"LICENSE": {
"owners": [
"@acme/platform"
],
"rule": "*",
"line": 2
}
},
"unparsed": [],
"source": "report",
"reports": [
{
"path": "CODEOWNERS",
"modified": "2026-03-04T10:15:00Z",
"format": "codeowners"
}
]
}