CODEOWNERS: Automating Code Review Ownership¶
Most projects have a moment when someone merges a change to a critical file without the right people ever seeing it. Not because anyone meant to skip the review, but because nothing in the workflow made it obvious who should have been asked. A security-sensitive configuration file, a shared library that dozens of services depend on, a public API contract: all of them can drift in the wrong direction when ownership is implied rather than explicit.
A CODEOWNERS file solves that problem. It maps paths and patterns in your repository to the people and teams responsible for reviewing them. When someone opens a pull request or merge request that touches those paths, the platform automatically requests a review from the designated owners. No manual assignment required, no institutional knowledge needed, and no way to merge without the right sign-off if you enforce it with branch protection.
This post covers what a CODEOWNERS file is, how to construct one, and how to use it effectively in your repositories.
What Is a CODEOWNERS File?¶
A CODEOWNERS file is a plain-text file that defines ownership rules for paths in a repository. Each rule maps a file pattern to one or more owners, expressed as usernames, team names, or email addresses. When a pull request or merge request is opened, the platform reads the file, determines which rules match the changed files, and automatically requests reviews from the corresponding owners.
On GitHub, the file also integrates with branch protection rules. You can require that at least one code owner approves a pull request before it can be merged, independently of the general required-reviewers count. GitLab uses the same concept for merge request approvals, with its own approval rule integration.
The format is intentionally minimal:
Lines beginning with # are comments. Everything else is a pattern followed by one or more owners separated by spaces.
Where to Put the File¶
The location of the file matters. The platform checks specific locations in a defined order, and the first file found is the one used.
GitHub checks three locations, in this order:
CODEOWNERS(repository root).github/CODEOWNERSdocs/CODEOWNERS
The first file found at any of these locations is used. Only one CODEOWNERS file is active at a time per repository.
Prefer .github/CODEOWNERS
Placing the file in .github/ keeps the repository root clean and co-locates it with other GitHub-specific configuration files like issue templates, workflow definitions, and pull request templates.
GitLab checks two locations in this order:
CODEOWNERS(repository root).gitlab/CODEOWNERS
As with GitHub, only one file is active per repository.
Prefer .gitlab/CODEOWNERS
Placing the file in .gitlab/ keeps the repository root clean and co-locates it with other GitLab-specific configuration files.
Syntax and Pattern Rules¶
Basic Syntax¶
Each non-comment line in a CODEOWNERS file has the form:
Owners can be:
- Individual users:
@username(the user's handle on the platform) - Teams:
@org/team-name(GitHub) or@group/subgroup(GitLab) - Email addresses:
[email protected](the email must be verified on the user's account)
Multiple owners on a single line means all of them will be requested for review when the pattern matches.
Pattern Matching¶
CODEOWNERS uses glob-style patterns. The rules are straightforward but have a few important behaviors to know:
| Pattern | Matches |
|---|---|
* | Any file in the repository |
*.go | Any .go file anywhere in the repository |
**/*.go | Any .go file anywhere in the repository |
/docs/ | Everything inside the docs/ directory |
/docs/*.md | All .md files directly inside docs/, not in subdirectories |
/docs/** | Everything inside docs/, including subdirectories |
/config.yml | Exactly that one file at the root |
*.tf | Any .tf file anywhere in the repository |
A pattern starting with / is anchored to the repository root. A pattern without a leading / can match anywhere in the tree. This means *.go and **/*.go behave identically: both match .go files at any depth.
A pattern ending with / matches the entire contents of that directory.
Last Match Wins¶
The most important rule in CODEOWNERS is that the last matching pattern takes precedence. If two rules match the same file, the one that appears later in the file wins. This makes the ordering of rules significant: put your broadest rules first and your most specific rules last.
# All files default to the project lead
* @peter-gibbons
# Go source files go to the backend team
**/*.go @office-space-example/backend
# But the auth package is always reviewed by security
/src/auth/** @office-space-example/security
In this example, a file at /src/auth/handler.go matches all three rules, but the last matching rule (/src/auth/**) wins, so @office-space-example/security is requested for review, not @peter-gibbons or @office-space-example/backend.
If you want both the security team and the backend team on auth files, list both owners:
No Negation¶
GitHub and GitLab CODEOWNERS do not support negation patterns. You cannot use !pattern to exclude files from a previous rule. If you need a path excluded from ownership, structure your rules so that no rule matches it, or assign it to a different owner.
Defining Owners¶
Individual Users¶
The simplest form of ownership assigns a single user by their platform handle:
This is the typical pattern for personal or solo projects: a single owner for the entire repository. If you are the only meaningful reviewer, there is no need for team-level granularity.
For projects where specific people own specific areas, assign by path:
# Peter Gibbons owns the core platform code
/platform/ @peter-gibbons
# Documentation team members own all Markdown
**/*.md @peter-gibbons @michael-bolton
Teams (Organization Repositories)¶
In an organization, you can assign ownership to an entire team rather than listing individuals. Teams are referenced as @org/team-name on GitHub and @group/subgroup on GitLab.
# Default owners
* @office-space-example/platform-team
# Backend services
/services/** @office-space-example/backend-team
# Infrastructure and configuration
*.tf @office-space-example/infrastructure-team
/kubernetes/ @office-space-example/infrastructure-team
# Security-sensitive paths always require the security team
/src/auth/ @office-space-example/security-team @office-space-example/backend-team
/.github/ @office-space-example/platform-team
Teams must have at least Read access to the repository to receive review requests. For teams to count toward required reviews, they typically need Write or Maintain access.
# Default owners
* @office-space-example/platform-team
# Backend services
/services/** @office-space-example/backend-team
# Infrastructure and configuration
*.tf @office-space-example/infrastructure-team
/kubernetes/ @office-space-example/infrastructure-team
# Security-sensitive paths
/src/auth/ @office-space-example/security-team @office-space-example/backend-team
/.gitlab/ @office-space-example/platform-team
GitLab references groups and subgroups using @group/subgroup syntax. The group or subgroup must have access to the project for the ownership rule to take effect.
Email Addresses¶
Owners can also be specified by email address:
/ops/ [email protected]
The email address must be verified on the user's account on the platform. If it is not verified, or if no account has that email address, the ownership rule silently has no effect.
Prefer Usernames Over Emails
Email-based ownership is fragile. Email addresses change, and the association between an email address and a platform account is not always obvious to other contributors. Use @username or @org/team references where possible.
Permission Requirements and Caveats¶
This is where a lot of projects trip up. A CODEOWNERS entry is only meaningful if the named owner actually has permission to review and approve changes in the repository.
On GitHub, an owner listed in CODEOWNERS must meet these requirements for review requests and required approvals to work:
-
The user must have at least Read access to the repository. Contributors with no access at all cannot be requested for review. For public repositories, any GitHub user can be listed; for private repositories, they must be a collaborator.
-
For required approvals to count (branch protection), the user must have Write or Maintain access. A reviewer with only Read access can leave a review comment, but their approval does not satisfy a required-review branch protection rule requiring code owner approval.
-
Teams must have explicit repository access. Listing
@office-space-example/security-teaminCODEOWNERShas no effect if that team has never been granted access to the repository. The team access level must be at least Write for approvals to count. -
Organization teams only. The
@org/team-namesyntax only works in repositories that belong to an organization. Personal repositories cannot use team ownership. -
Fork pull requests use the base repository's
CODEOWNERS. When a contributor opens a pull request from a fork, theCODEOWNERSfrom the base repository is used. The fork's own file, if any, is ignored.
Silent Failures
If a listed owner does not have the required access, GitHub silently skips them when auto-assigning reviews. No error appears in the pull request. The review request simply never happens. Always verify that listed owners have the necessary repository access after adding them to the file.
On GitLab, the same principle applies with slightly different terminology:
-
The user or group must be a member of the project. Ownership rules referencing users or groups without project membership are silently ignored.
-
Approval eligibility requires at least Developer access. Users with Reporter or lower access can be notified but cannot approve merge requests in a way that satisfies approval rules.
-
Group-level ownership works across projects. If a group is listed as an owner, any member of that group with the required access level can satisfy the approval requirement.
-
Approval rules must be configured. Adding a path to
CODEOWNERSdoes not automatically enforce approval. You must enable Require approval from code owners in the project's merge request approval settings under Settings → Merge requests.
Structuring Ownership by Project Size¶
Personal Repositories (No Organization)¶
For a personal repository, teams are not available. Ownership is limited to individual users and email addresses. The most common pattern is a single default rule covering the entire repository:
This is useful even on personal projects because it ensures that if a collaborator is added to the repository and opens a pull request, you are always automatically requested for review. Without it, reviews must be requested manually.
For personal repositories with a small number of trusted collaborators:
# .github/CODEOWNERS
# Default: Peter Gibbons reviews everything
* @peter-gibbons
# Michael Bolton co-owns the documentation
/docs/ @peter-gibbons @michael-bolton
# Samir Nagheenanajar owns the CLI tooling
/cmd/ @peter-gibbons @samir-nagheenanajar
The project lead (@peter-gibbons) remains on all rules so they always get a review request, while domain experts are added to the areas they own.
Small to Mid-Size Open-Source Projects¶
For a project hosted under a personal account but with regular external contributors, a layered approach works well:
# .github/CODEOWNERS
# Catch-all: maintainer reviews everything
* @peter-gibbons
# Go source requires the maintainer and any Go-focused contributors
**/*.go @peter-gibbons @michael-bolton
# CI/CD and release configuration is sensitive
/.github/workflows/ @peter-gibbons
/.goreleaser.yml @peter-gibbons
/Taskfile.yml @peter-gibbons
# Documentation can be reviewed by the docs contributor
/docs/ @peter-gibbons @milton-waddams
# Dependency files get extra attention
go.sum @peter-gibbons
go.mod @peter-gibbons
The catch-all rule ensures nothing falls through unreviewed. Sensitive files like CI workflows and dependency manifests are pinned to the maintainer directly, preventing a contributor from silently modifying the release pipeline or injecting a malicious dependency.
Large Organizational Repositories¶
Organizational repositories benefit most from CODEOWNERS because they tend to have multiple teams with distinct areas of responsibility. A well-structured file makes review ownership self-documenting. The example below is for a TypeScript/Node.js monorepo with a frontend, several backend services, and shared infrastructure:
# .github/CODEOWNERS
# Default: platform team owns anything not explicitly covered
* @office-space-example/platform-team
# ----- Services -----
/services/api/ @office-space-example/api-team
/services/auth/ @office-space-example/security-team @office-space-example/api-team
/services/billing/ @office-space-example/billing-team @office-space-example/platform-team
/services/worker/ @office-space-example/backend-team
# ----- Frontend -----
/web/ @office-space-example/frontend-team
**/*.tsx @office-space-example/frontend-team
**/*.css @office-space-example/frontend-team
# ----- Infrastructure -----
*.tf @office-space-example/infrastructure-team
/kubernetes/ @office-space-example/infrastructure-team
/helm/ @office-space-example/infrastructure-team
/docker-compose.yml @office-space-example/infrastructure-team
# ----- CI/CD -----
/.github/workflows/ @office-space-example/platform-team
/.github/actions/ @office-space-example/platform-team
# ----- Documentation -----
/docs/ @office-space-example/docs-team
# ----- Dependencies -----
package.json @office-space-example/platform-team
package-lock.json @office-space-example/security-team
tsconfig.json @office-space-example/platform-team
# ----- Security-Sensitive Configuration -----
/config/secrets.* @office-space-example/security-team
/src/auth/ @office-space-example/security-team @office-space-example/api-team
/src/crypto/ @office-space-example/security-team
A few patterns worth noting here:
- Security team on
authandcryptopaths ensures no authentication or cryptographic code merges without a security review. - Dependency manifests assigned to the security team catches supply chain changes.
- CI/CD workflows owned by the platform team prevents lateral privilege escalation through workflow modifications.
- Double-ownership on billing (
@office-space-example/billing-teamand@office-space-example/platform-team) provides a backstop when the billing team is unavailable. - Comments used as section headers make the file readable at a glance.
GitLab Sections¶
GitLab supports an additional feature that GitHub does not: CODEOWNERS sections. A section groups related rules under a named header and allows project settings to configure approval requirements per section independently.
# .gitlab/CODEOWNERS
[Documentation]
*.md @office-space-example/docs-team
[Backend]
**/*.go @office-space-example/backend-team
[Infrastructure]
*.tf @office-space-example/infrastructure-team
/kubernetes/ @office-space-example/infrastructure-team
# The owner on the section header is the default for all entries below.
# Entries without an explicit owner inherit @office-space-example/security-team.
[Security] @office-space-example/security-team
/src/auth/
/src/crypto/
*.sum
A section can have a default owner listed on the section header line itself, which applies to all entries in that section unless overridden by a more specific inline owner. In the example above, /src/auth/, /src/crypto/, and *.sum all inherit @office-space-example/security-team from the section header.
Sections marked with ^ are optional: approval from those owners is requested but not required for the merge request to proceed.
This is useful for sections that represent advisory review rather than a hard gate.
Auto-Assigning Reviews¶
How It Works¶
When a pull request or merge request is opened (or updated with new commits), the platform reads the CODEOWNERS file and matches the changed file paths against the ownership rules. For each matched owner, a review is automatically requested.
The auto-assignment happens without any contributor action. The PR author does not need to know who owns the files they modified. This is the primary operational value of the file for teams: ownership is encoded in the repository, not in someone's head.
Enabling Required Code Owner Reviews¶
Auto-requesting reviews is convenient, but it does not enforce that those reviews actually happen. For that, you need branch protection rules.
In GitHub, go to Settings → Branches → Branch protection rules and either create or edit the rule for your default branch (typically main).
The key settings are:
- Require a pull request before merging: Enables the review requirement.
- Require approvals: Sets the minimum number of approvals needed. Set this to at least
1. - Require review from Code Owners: When checked, at least one code owner for the changed files must approve before the pull request can merge. This is independent of the general approvals count.
- Dismiss stale pull request approvals when new commits are pushed: Ensures that a previous approval is invalidated when new changes are pushed, requiring re-approval.
Code Owner Approval Is Additive
With Require review from Code Owners enabled, the branch protection rule requires both the general approval count and at least one code owner approval. Setting required approvals to 1 and enabling code owner reviews means a PR needs one approval, and that approval must come from a code owner of the changed files.
For maximum protection on high-value branches:
- Enable Require approvals with a count of
1or higher. - Enable Require review from Code Owners.
- Enable Dismiss stale pull request approvals when new commits are pushed.
- Enable Restrict who can push to matching branches to limit direct pushes.
In GitLab, enable code owner approval enforcement under Settings → Merge requests. Look for Require approval from code owners and enable it.
You can also configure per-branch enforcement under Settings → Repository → Protected branches. For each protected branch, you can set Code owner approval to required.
GitLab's approval rules system integrates with CODEOWNERS sections: you can configure how many approvals are required from each section's owners independently under Settings → Merge requests → Approval rules.
Protected Branch Required
In GitLab, code owner approval enforcement only applies to protected branches. A branch that is not protected will not enforce CODEOWNERS approval even if the project-level setting is enabled.
Multiple Reviewers and Review Teams¶
When multiple owners are listed on a single rule, all of them are automatically requested for review. However, on GitHub, only one of them needs to approve for the code owner requirement to be satisfied (unless your branch protection requires more):
When a pull request touches /src/auth/, both @peter-gibbons and @michael-bolton receive review requests. If either one approves and the code owner requirement is enabled, the requirement is met.
For cases where you need all owners to approve, GitHub does not provide a native mechanism via CODEOWNERS alone. You would need to use a separate required-reviewers rule or a workflow that enforces multi-party approval.
Team Review Assignment on GitHub
When a GitHub team is listed as an owner, GitHub automatically requests a review from the team. GitHub will also optionally assign individual members of the team based on your team's Code review assignment settings, which can be configured under your organization's team settings. This allows you to load-balance review requests across team members rather than notifying every member of a large team at once.
Validating Your CODEOWNERS File¶
Common Mistakes¶
A few mistakes appear frequently in CODEOWNERS files:
-
Patterns that never match: A typo in a path, a leading
/missing where one is expected, or a**that does not span the intended depth. -
Owners without repository access: An owner listed who has no access to the repository. Reviews are silently not requested.
-
Non-existent teams: A team name that does not exist in the organization, or has since been renamed or dissolved.
-
Outdated usernames: A contributor whose GitHub handle has changed, or who has left the project.
-
File in the wrong location: A
CODEOWNERSfile in a directory the platform does not check, or multipleCODEOWNERSfiles where only one is active.
Platform Validation¶
GitHub surfaces CODEOWNERS errors directly in the repository interface. Navigate to the file in the GitHub web UI and look for the Syntax errors link that appears at the top of the file view when the file contains invalid syntax or references to users and teams that cannot be resolved.
You can also check the state of your CODEOWNERS file through the GitHub API:
This returns a JSON object listing any parse errors, unresolvable owners, and other problems the platform has detected. GitHub will report errors for:
- Syntax errors in the file
- References to users who do not exist or cannot be resolved
- References to teams that do not exist in the organization
- References to email addresses that cannot be matched to a user
GitLab surfaces validation feedback in the merge request interface. When a merge request is opened, a warning banner appears if the CODEOWNERS file contains unresolvable entries. The merge request will still proceed, but the invalid ownership rules will be silently skipped.
A Complete Example¶
Here is a complete, annotated CODEOWNERS file for a mid-size TypeScript/Node.js organizational repository with a frontend application and shared backend services:
# .github/CODEOWNERS
#
# Documentation: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
#
# Rules are evaluated in order from top to bottom.
# The LAST matching rule takes precedence.
# Owners must have at least Write access to the repository for approvals to count.
# ----- Default -----
# All files not explicitly covered below require the platform team.
* @office-space-example/platform-team
# ----- Source -----
/src/ @office-space-example/backend-team
/src/auth/ @office-space-example/security-team @office-space-example/backend-team
/src/crypto/ @office-space-example/security-team
# ----- Frontend -----
/web/ @office-space-example/frontend-team
**/*.css @office-space-example/frontend-team
**/*.tsx @office-space-example/frontend-team
# ----- Infrastructure -----
*.tf @office-space-example/infrastructure-team
/kubernetes/ @office-space-example/infrastructure-team
/helm/ @office-space-example/infrastructure-team
# ----- CI/CD -----
/.github/ @office-space-example/platform-team
# ----- Documentation -----
/docs/ @office-space-example/docs-team
# ----- Dependencies (require security review) -----
package.json @office-space-example/platform-team
package-lock.json @office-space-example/security-team
tsconfig.json @office-space-example/platform-team
Best Practices¶
Keep the file organized with comments. Comments make the file self-documenting. Use them as section headers and to note any non-obvious ownership decisions, such as why the security team is on dependency manifests.
Use the most specific path you can. Broad patterns like * are useful as fallbacks, but specific paths minimize accidental ownership overlap and make the intent of each rule clear.
Put the file in .github/ or .gitlab/. Keeping platform-specific files out of the repository root improves discoverability and reduces clutter.
Review the file when team structure changes. When teams are renamed, split, or dissolved, update the CODEOWNERS file. An ownership rule referencing a team that no longer exists silently stops working.
Pair with branch protection rules. The file is informational without branch protection. If you want ownership to actually gate merges, enable Require review from Code Owners on your protected branches.
Assign a default owner for the entire repository. A * rule at the top of the file ensures that every file has an owner, even files added in new directories or with unexpected extensions. Without a default, new paths may have no owner and receive no automatic review request.
Do not over-assign. Adding every senior engineer to every rule is the same as having no ownership. If everyone owns everything, no one feels responsible. Keep ownership narrow enough that the assigned reviewer has specific context and a real stake in the quality of those files.
Audit the file periodically. At the start of each quarter, review the file against your current team structure and repository layout. Remove stale owners, add new team members, and adjust paths that have been reorganized.