In a GitHub CODEOWNERS file, which rule applies when several patterns match the same file?
answer
- Order matters more than you expect
- Two matching lines do not combine
- Read the file bottom-up to find the winner
- Last match, evaluated per changed file
basics
~20 sThe last matching line in the file wins, and only that line's owners apply. Ownership is never additive across rules, so an earlier specific rule loses to a later broad one — write general patterns first and specific ones last.
solid answer
~40 sGitHub evaluates CODEOWNERS **per changed file**, top to bottom, and the **last** pattern that matches that file decides its owners. Specificity is irrelevant: a trailing `*` line placed at the bottom overrides every carefully targeted rule above it. Ownership is also not cumulative — the winning line's owners are the only owners, so an earlier team is not silently added alongside a later one. The practical convention is to order the file from broadest to narrowest: a catch-all at the top, then directory-level rules, then the exceptions. A pull request touching several files can therefore require several different owners, one set per file, and where code owner review is required the merge is blocked until each owned file has an approving review from one of its owners.
code
text · 6 lines# broad first, exceptions last — the last match wins
* @my-org/platform
/docs/ @my-org/tech-writers
/docs/api/ @my-org/api-team
/infra/ @my-org/sre @my-org/security
/.github/CODEOWNERS @my-org/platformgo deeper
Remember one sentence: the last matching line in a GitHub CODEOWNERS file wins. If you recall nothing else, recall that specificity does not decide it.
Explain the mechanics — evaluation is per changed file, top to bottom, last match only, owners never accumulate — and state the broad-to-narrow ordering convention that follows from it.
Show how you debug it in a real repository: read the file bottom-up for the first line matching the path, and treat an appended broad rule as the usual cause of a team silently losing ownership.
Own the maintenance policy for a large file — enforced ordering, review of CODEOWNERS diffs for appended catch-alls, and the fact that per-file resolution is what turns cross-cutting changes into multi-team sign-offs.
## The rule A GitHub `CODEOWNERS` file is a list of `<pattern> <owners…>` lines. To decide who owns a file, GitHub walks the file from top to bottom and keeps the **last** line whose pattern matches that path. That line's owners are the file's code owners. Every earlier match is discarded. Two consequences follow, and interviewers probe both: 1. **Order beats specificity.** A pattern is not "more authoritative" because it is longer or narrower. If `/docs/api/ @my-org/api-team` appears above `* @my-org/platform`, then `docs/api/index.md` is owned by the platform team, because the catch-all matched last. 2. **Ownership is not additive.** The winning line's owners replace, not extend, the previous ones. If you want two teams to both be able to approve a path, both must be named on the same winning line — and even then it is an *or*, since one approval from either satisfies it. ## The conventional ordering Because the last match wins, the file reads best from broadest to narrowest: a catch-all first, then directory rules, then the carve-outs. Reading downward, later lines carve exceptions out of earlier ones. Reversing the order would collapse the whole file into a single owner. This is also why an appended line is dangerous. The natural instinct when adding a rule is to append it at the end of the file; if that rule is broad, it silently steals ownership from every narrower rule above it. In a large file, teams review CODEOWNERS diffs specifically for lines added below existing rules. ## Per-file evaluation Ownership resolution happens per changed path, not per pull request. A pull request touching `docs/api/openapi.yaml`, `infra/main.tf` and `src/app.ts` under a typical file has three distinct owner sets. GitHub requests all of them, and if the branch requires review from code owners, the pull request stays blocked until each owned path has an approving review from one of *its* owners. One team cannot approve on behalf of another's files. This is the mechanism behind the familiar complaint that a wide-reaching change needs many sign-offs: it is not a special "big PR" rule, just per-file resolution applied to a diff that happens to cross ownership lines. ## Pattern matching itself The patterns use gitignore-style syntax, which affects what "matches" means before precedence is even considered: - `*` used alone as the whole pattern matches any file at any level. - A leading slash anchors to the repository root: `/build/` matches only the top-level `build` directory, while `build/` matches one at any depth. - A trailing slash means the directory and everything under it. - `docs/*` matches files **directly inside** `docs` but not inside its subdirectories — a frequent source of "why wasn't this owned?". Getting the pattern wrong produces the same symptom as getting the order wrong (the intended owner is not requested), so debugging usually means checking both: does the pattern match this path at all, and is it the *last* pattern that does? ## Interaction with enforcement Precedence only decides who is requested and whose approval counts. Whether an unapproved pull request can still merge is a separate setting on the target branch — required review from code owners, configured through branch protection or a repository ruleset. Without it, precedence still determines who gets the review request, but nothing blocks the merge. ## Reviewing a large file At monorepo scale the file grows to hundreds of lines, and the failure mode is subtle: a rule exists, looks right, and never fires because something below it matches too. Two habits help. First, keep the file sorted broad-to-narrow and enforce that in review. Second, when someone reports that the wrong team was asked, read the file **bottom-up** for the first line matching the path in question — that is the winner, and it is usually a catch-all someone appended months earlier.
- How do you make a path require both the security team and its owning team?You cannot express an AND in CODEOWNERS — the last matching line's owners are alternatives. Naming both teams on that line means either can approve. Requiring two independent sign-offs needs a different control: raising the required approving reviews on the branch, or splitting the change so different rules own different paths.
- Someone appended a broad rule at the end of a large CODEOWNERS file. What breaks?Every narrower rule above it that its pattern also matches stops taking effect, because the appended line now matches last. The symptom is that the specialist teams quietly stop being requested and one team is asked for everything. The fix is to move the broad rule to the top, where catch-alls belong.
- A pull request changes files owned by three different teams. What is required to merge it?Resolution is per file, so the pull request carries three owner sets and GitHub requests all of them. Where the branch requires code owner review, each owned path needs an approving review from one of its own owners; one team's approval does not cover another team's files.
It behaves like CSS with no specificity scoring at all: whichever rule appears last simply wins, so the file must be ordered general to specific by hand.
saying these in an interview costs you the question
- Claiming the most specific pattern wins
- Assuming owners from several matching rules are combined
- Appending new rules to the bottom of the file
- Thinking one approval covers every owned path in a PR
- Believing precedence alone blocks the merge