CODEOWNERS and Branch Protection Best Practices
CODEOWNERS says who must review which files. Branch protection (or rulesets) says what must be true before code reaches main. Together they are the cheapest quality and security controls you have, if they are set up so that people do not route around them. This guide covers the syntax and the settings that matter.
How CODEOWNERS works
A CODEOWNERS file maps path patterns to users or teams. GitHub looks for it in .github/, the repository root or docs/, in that order, and uses the first one it finds. When a PR changes matching files, GitHub automatically requests review from the listed owners. If branch protection requires code owner review, the PR cannot merge without approval from an owner.
# Default owners for everything
* @acme/platform
# Frontend
/web/ @acme/frontend
*.css @acme/frontend @acme/design
# Backend and database
/services/api/ @acme/backend
/db/migrations/ @acme/backend @acme/dba
# CI and security-sensitive files
/.github/ @acme/platform @acme/security
/infra/ @acme/platform
Dockerfile @acme/platform
The rules, from GitHub's docs:
- The last matching pattern wins. Order from general to specific. A file matching two lines takes the owners of the later line only, not both.
- Patterns follow
.gitignorerules, with exceptions: you cannot use!negation, and[ ]character ranges are not supported. - Owners are
@username,@org/team-nameor an email address linked to an account. - Owners must have write access to the repository, and a team must be visible and have write access. Otherwise the line is ignored.
- A file over 3 MB is not read.
- Invalid lines are flagged in the file view's error list; check it after edits.
Generate a starting file with the CODEOWNERS generator, then read through it as a reviewer would.
Use teams, not individuals
Individuals leave, go on holiday and become bottlenecks. Assign teams, and let GitHub's team review assignment pick one or more members (round robin or load balance) in the team's settings. For very large teams, enable "Auto assignment" so notifications do not go to everyone.
Keep ownership broad enough that a PR always has someone available, and narrow enough to mean something. A file where every PR needs five approvals teaches people to rubber-stamp.
Protect the paths attackers want
Give a trusted team ownership of:
/.github/and especially/.github/workflows/. A workflow change can access secrets and deploy; see securing GitHub Actions.CODEOWNERSitself, so the rules cannot be changed by someone who owns nothing.- Dependency manifests and lockfiles, if supply-chain review matters to you.
- Infrastructure code, auth and billing logic, and migrations.
Branch protection or rulesets?
Rulesets are the newer mechanism. They can layer, apply to many branches by pattern, work across an organization, and have bypass lists that are audited. Branch protection rules are older but still supported. Either way, set these on main and release branches:
| Setting | Recommendation | Why |
|---|---|---|
| Require a pull request before merging | On | No direct pushes |
| Required approvals | 1, or 2 for sensitive repos | Review for every change |
| Dismiss stale approvals on new commits | On | An approval should cover the code that merges |
| Require review from Code Owners | On | Makes CODEOWNERS binding |
| Require approval of the most recent push | On | The author cannot add commits after approval and self-merge |
| Require status checks | On, with named required checks | CI must pass |
| Require branches to be up to date | On, or use a merge queue | Avoids semantic conflicts |
| Require conversation resolution | On | Comments get answered |
| Require signed commits | Optional | Needed in some compliance regimes |
| Require linear history | Optional | Matches squash or rebase merging |
| Block force pushes and deletions | On | Protect history |
| Do not allow bypassing | On for everyone, admins included | "Admins can override" tends to be used at the worst time |
Required checks should be few and stable: tests, lint, build and a security scan. Name them through a single summary job so that renaming matrix cells does not break the rule; see matrix builds done right. A required check that is skipped by a paths filter stays pending forever, so handle it in the workflow.
Avoid the common failure modes
- Code owner review on a repository with one maintainer. The author cannot approve their own PR, so a lone owner is stuck. Add a second owner or use a team of at least two.
- Owner not in the repository's write list. The line is silently ignored.
- **
catch-all with a huge team.* Everyone is requested on every PR, so nobody feels responsible. - Bots. Dependabot and Renovate PRs need owners too, so they are not stuck. Route them to a rotating dependency-owner team and use auto-merge for low-risk updates. See Dependabot setup.
- Required checks that are flaky. People start using admin bypass. Fix the flakes; see dealing with flaky tests.
- Bypass lists that grow. Review them each quarter.
Review hygiene that goes with it
- Keep PRs small. Reviewers approve large diffs without reading them.
- Add a PR template with a checklist for the risky paths.
- Track review latency; slow reviews stretch lead time, one of the DORA metrics.
- Use required reviews from owners for risky paths and a lighter process for docs. CODEOWNERS can express that: assign docs to a broad team.
Test it
After changing rules, open a throwaway PR that touches each protected area and confirm the right people are requested and the merge button is blocked. In the CODEOWNERS file view, GitHub shows syntax errors; fix them first.
FAQ
Where should the CODEOWNERS file live?
.github/CODEOWNERS is the common choice. GitHub also reads the repository root and docs/.
Can one file have several owners?
Yes. List several owners on one line. Any one of them can satisfy the code owner requirement (not all of them), unless you create separate requirements with rulesets.
Do code owners apply to the PR author?
An owner who authors the PR cannot approve it themselves, so another owner has to.
Is the main branch the only one that needs this?
Protect any branch that deploys, such as release/* or production. A ruleset with a branch pattern does this in one place.
Related
Pair this with the Dependabot guide so update PRs get an owner. Faster required checks also help people stay inside the process: compiler.dev's comparison mode shows how much faster your required checks run on faster runners.
Made by compiler.dev. Free tools · Pricing