Code OwnershipMarch 14, 2026 · 12 min read

How to set up CODEOWNERS in GitHub (and actually enforce it)

Most teams know about CODEOWNERS. Few actually enforce it. Fewer still keep it accurate over time. This is how to do all three.

What is CODEOWNERS and why does it matter

CODEOWNERS is a GitHub file that maps file paths to the teams or individuals who are responsible for reviewing changes to those paths. When a pull request touches a file covered by a CODEOWNERS rule, GitHub automatically requests a review from the designated owner and — when branch protection is configured correctly — requires that review before merge.

On its face this sounds simple. In practice, most CODEOWNERS files are incomplete, stale, or not enforced at the branch protection level — making them documentation rather than gates. The difference between a CODEOWNERS file that is documentation and one that is enforcement is everything for code quality and compliance.

There are three valid locations for a CODEOWNERS file in a GitHub repository, and GitHub checks them in this exact order:

  1. Repository rootCODEOWNERS
  2. .github directory .github/CODEOWNERS
  3. docs directorydocs/CODEOWNERS

GitHub stops at the first file it finds. Most teams use .github/CODEOWNERS to keep the root clean — either location works identically.

The compliance angle is real and growing. SOC 2 Type II requires evidence of code review controls — specifically that a second person with appropriate expertise reviews code before it merges to production. CODEOWNERS, combined with branch protection requiring code owner review, is the mechanism that produces that evidence automatically. Without it, you are collecting screenshots and explaining exceptions to auditors.

CODEOWNERS file syntax — a complete reference

CODEOWNERS syntax is based on gitignore patterns with one critical difference: the last matching rule wins. This is the most common source of confusion for teams setting up CODEOWNERS for the first time. A rule earlier in the file can be completely overridden by a more specific rule later.

Here is a complete annotated example covering the most common patterns:

# Global fallback — everyone's responsible = no one is
* @org/platform-team

# Specific directories — trailing slash matches directory and contents
/src/payments/ @org/payments-team @alice

# Infrastructure — all Terraform files regardless of location
/infra/ @org/devops-team

# File extension patterns
*.tf @org/devops-team
*.sql @org/data-team

# Subdirectory wildcard — matches one level of nesting
/src/*/api/ @org/backend-team

# Deep wildcard — matches any depth
/src/**/migrations/ @org/data-team

# Negation — exclude a subdirectory from the parent rule
/docs/ @org/docs-team
!/docs/api/ @org/backend-team

Several gotchas are worth calling out explicitly because they have burned many teams:

* does not match subdirectories. The pattern *.ts matches server.ts at the root but does not match src/server.ts. To match TypeScript files anywhere in the repository, you need **/*.ts. This distinction trips up almost every team the first time.

A missing trailing slash changes the match. The pattern /infra (no trailing slash) matches both a file named infra at the root and a directory named infra/. Adding the trailing slash /infra/ makes the intent explicit and only matches the directory. Always use trailing slashes for directory patterns.

Last rule wins — always. If you write * @org/platform-team at the top and then /src/payments/ @org/payments-team later, files in /src/payments/ require review from @org/payments-team, not @org/platform-team. The later, more specific rule supersedes the earlier one. Always put your catch-all at the top and specific rules below it.

Team vs individual owners — which to use

GitHub CODEOWNERS supports two kinds of owner references: GitHub teams using the @org/team-slug format, and individual users using @username. Both work, but they have meaningfully different operational characteristics.

Teams are the right choice for almost every situation. When you write @org/payments-team in CODEOWNERS, any member of that GitHub team can satisfy the code owner review requirement. When someone joins the team, they automatically become a valid reviewer. When someone leaves, removing them from the GitHub team is sufficient — the CODEOWNERS file does not need to change. For teams with multiple members, GitHub will request review from one or two members automatically; any approval satisfies the requirement.

The one prerequisite for teams is that your GitHub organization must have GitHub teams configured with the correct membership. This is a one-time setup cost that pays off immediately.

Individual users are tempting for small teams or solo ownership situations, but they do not scale and they create operational risk. If you write @alice as the owner of the payments module and Alice leaves the company, her GitHub account is removed from the organization — and GitHub will silently fail to request a review from her, with no warning in the pull request. The PR will appear to have no code owner, which effectively bypasses enforcement.

The recommendation: always use teams, even if a team has only one member today. Create a GitHub team with a single member rather than listing the individual directly. When that person changes roles or leaves, updating team membership is a single action in GitHub settings rather than a CODEOWNERS file edit that requires a PR.

Common CODEOWNERS mistakes (and how to avoid them)

After analyzing CODEOWNERS files across hundreds of repositories, these are the patterns that consistently cause problems:

1. The catch-all trap. Writing * @every-team — assigning every file to every team — sounds thorough. In practice it means every pull request requires approval from every team lead, which means nobody reviews anything meaningfully because everyone is waiting for someone else. A catch-all is only useful if it resolves to a single, genuinely responsible team. If your catch-all resolves to more than two teams, remove it.

2. Patterns that do not match what you think. The difference between *.ts and **/*.ts is not obvious from the syntax, but it is the difference between matching zero TypeScript files (because all your TypeScript is in subdirectories) and matching all of them. Always test your patterns against your actual file tree before assuming they work.

# This matches ONLY TypeScript files in the repository root
*.ts @org/backend-team

# This matches TypeScript files ANYWHERE in the repository
**/*.ts @org/backend-team

3. Deleted team members break rules silently. When a user listed as an individual CODEOWNERS owner is removed from the GitHub organization — whether due to off-boarding, a name change, or an account migration — GitHub does not display a warning in pull requests. The rule simply stops functioning. There is no alert, no PR comment, no notification. This is particularly dangerous because teams often discover the problem months later when an auditor asks for proof of code owner review and the audit trail shows none.

4. Copy-paste from another repository. Duplicating a CODEOWNERS file from one repository to another is common when spinning up new services. The problem is that team slugs — @org/payments-team, @org/devops-team — may map to completely different teams or may not exist at all in the new repository's organization. GitHub will not flag invalid team references at commit time. You will only discover them when a PR is opened and no review is requested.

5. No process to update CODEOWNERS when ownership changes. CODEOWNERS files are written once during project setup and then treated as permanent fixtures. Teams merge, split, get renamed, and take on new responsibilities — and the CODEOWNERS file reflects none of it. Within six months of initial setup, most CODEOWNERS files have at least one rule that points to a team that no longer owns the relevant code. Within a year, many are substantially inaccurate.

How to audit CODEOWNERS compliance

GitHub provides a CODEOWNERS validation endpoint that checks whether a repository's CODEOWNERS file is syntactically valid and whether all referenced users and teams exist. You can access it at GET /repos/{owner}/{repo}/codeowners/errors via the GitHub API. This is the first check to run — it surfaces broken references before they silently disable enforcement.

Beyond syntax validation, two audit questions matter most at the organizational level:

Which repositories have no CODEOWNERS at all? For organizations with dozens or hundreds of repositories, the absence of a CODEOWNERS file is the norm rather than the exception. Use the GitHub API to enumerate all repositories and check for CODEOWNERS file existence at each of the three valid paths. Any repository without one has no code owner enforcement, regardless of branch protection settings.

Which repositories have stale CODEOWNERS? A stale CODEOWNERS file is one where referenced teams no longer exist in the organization, referenced users are no longer organization members, or team membership has changed so dramatically that the team no longer represents the actual owners. Detecting staleness requires cross-referencing CODEOWNERS content against the GitHub teams API — not just validating file syntax.

The manual audit approach — checking each repository by hand — works for organizations with fewer than ten repositories. Above that threshold, manual audits become unreliable. Teams check at setup, then again when someone flags a problem, which is often never. The practical alternative is daily automated sync that compares CODEOWNERS state against the GitHub API and surfaces drift.

Enforcement without friction — the practical approach

GitHub's native enforcement mechanism is branch protection: navigate to Settings → Branches → Branch protection rules, select your default branch, and enable Require review from Code Owners. With this setting active, a pull request cannot be merged until at least one designated code owner has approved it.

This is the right setting for production-critical paths. It is also the source of the most common complaint about CODEOWNERS: it blocks PRs in ways that frustrate developers when ownership is ambiguous or when the designated owner is unavailable.

The friction problem is real. A PR that sits unmerged for 48 hours because the designated CODEOWNERS reviewer is on vacation — while a perfectly qualified team member has already approved — teaches developers to route around the process or to advocate for removing code owner requirements entirely. Strict block-on-failure enforcement optimizes for compliance at the expense of developer velocity.

The more sustainable model is configurable enforcement. Rather than blocking merges unconditionally, a PR check can operate in three modes:

  • Pass — code owner review received, no action needed.
  • Warn — no code owner review yet, but merge is not blocked. The PR author and owner are notified automatically.
  • Fail — merge is blocked until a code owner approves. Used for regulated paths (payments, auth, infrastructure).

This model — where enforcement is tiered based on the sensitivity of the code path — achieves accountability without creating friction on every pull request. The goal is accountability, not bureaucracy. Auto-notifying owners on PR open, rather than blocking at merge, resolves most compliance gaps before they become a problem.

Keeping CODEOWNERS accurate over time

The hardest part of CODEOWNERS is not setup — it is maintenance. Most engineering organizations can get a CODEOWNERS file into shape in an afternoon. Keeping it accurate six, twelve, and eighteen months later requires a process, not just intention.

The drift detection signal is simple: compare CODEOWNERS-designated owners against actual PR reviewers. If the CODEOWNERS file says @org/payments-team owns /src/payments/ but the last 30 PRs touching payments were reviewed by members of @org/backend-team, that is a signal that actual ownership has shifted and the CODEOWNERS file has not followed. This comparison can be run against GitHub's pull request review history via API.

Three triggers should prompt a CODEOWNERS review:

  • A team is renamed or reorganized in the GitHub organization
  • A new service or directory is added that is not covered by any existing rule
  • An engineer who is listed as an individual owner changes roles or leaves

Automating CODEOWNERS health measurement gives engineering leaders a coverage metric they can track over time: percentage of repositories with valid CODEOWNERS, percentage of file paths covered by at least one active owner rule, and number of stale or broken references by repository. Treating code ownership as a health metric — rather than a one-time configuration task — is what separates teams that actually enforce it from teams that have the file and wonder why reviews are still inconsistent.

Koalr syncs CODEOWNERS daily and alerts on drift — free for all plans

Connect your GitHub organization and Koalr will automatically audit CODEOWNERS coverage across every repository, surface broken owner references, detect drift between designated and actual reviewers, and show you a health score you can present to auditors.