Skip to main content
RunBook Academy

Git, CI/CD & GitOpsXXXI · CODEOWNERS and Ownership ControlsSyntax

Syntax and patterns — the line format, glob rules, and the order trap

Advanced⏱ ~21 mingit

What you'll learn

  • Write a CODEOWNERS line that pins an owner to a single file versus a directory tree
  • Predict which lines match a given path and which one wins on conflict
  • Distinguish leading-slash-anchored patterns from unanchored glob patterns
  • Identify the four common syntax mistakes that turn CODEOWNERS into documentation

Prerequisites

Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x

Not yet marked complete on this device.

A CODEOWNERS file is a sequence of lines, most of which pair a pattern with one or more owners. The format is deliberately small: anything you can do in CODEOWNERS, you can do in a few dozen lines. What makes the format hard to get right is the order rule and the semantics of leading slashes.

The line format

Each line is <pattern> <owner1> [<owner2> ...]. Comments start with #. Blank lines are ignored. On GitHub an optional [Section] header in brackets starts a new section but has no semantic effect.

# Default owners for everything in the repo
*                        @platform/platform-infra
/terraform/              @platform/platform-infra
/terraform/prod/         @platform/platform-infra @security-team
/prod/secrets.yaml       @security-team

A line with only a pattern and no owner is invalid; the forge will report it on push. A line that references a user or team that does not exist will silently fail to assign that owner - which is one of the ways CODEOWNERS becomes documentation.

Pattern semantics

Three rules govern matching:

  • A pattern without a leading slash matches anywhere in the repo. *.tf matches main.tf at the root and also modules/vpc/main.tf deep in the tree.
  • A pattern with a leading slash is anchored at the repo root. /main.tf matches only the file main.tf at the root; modules/vpc/main.tf is not matched by /main.tf.
  • A pattern ending with / matches directories recursively. /terraform/ matches every file under /terraform/.

Glob characters are * (single path segment), ** (any sequence of segments), ? (single character), and [abc] (character class).

cat .github/CODEOWNERS

A worked example, given terraform/prod/iam/main.tf:

*.tf            @platform/platform-infra
/terraform/     @platform/platform-infra
/terraform/prod/ @platform/platform-infra @security-team
main.tf         @platform/platform-infra

The file is matched by the first three lines and not by the fourth. All matching owners accumulate.

The order rule

When two lines match the same path, the last matching line wins. This is the rule that produces the most subtle bugs.

/terraform/     @platform/platform-infra
/terraform/prod/ @security-team

For a file under /terraform/prod/, both lines match; the second wins, so the owners are @security-team. For a file under /terraform/staging/, only the first line matches, so the owner is @platform/platform-infra.

flowchart LR
    A[Path under review] --> B[Walk CODEOWNERS top to bottom]
    B --> C{Line matches?}
    C -->|yes| D[Add owners to running set]
    D --> E{More lines?}
    E -->|yes| B
    E -->|no| F[Final set of owners]
    C -->|no| E

The “last match wins” rule applies to the primary assignment in GitLab semantics; on GitHub all matching lines accumulate owners. The semantic differences between forges matter and are revisited in the next lesson.

Validating the file

There is no canonical codeowners-lint, but every forge reports syntax errors on push. The practical validation is to open a PR that touches one path per rule, confirm the right reviewers are assigned, and confirm the merge is blocked until they approve.

git ls-files | grep CODEOWNERS

Production discipline

Treat CODEOWNERS changes as code changes: open a PR, get two reviewers, run a test PR through the forge, and confirm the reviewer list matches the expectation. A CODEOWNERS rule that has never been exercised against a real PR is a rule that has not been validated.

Cross-course references

  • Ansible for Production Sysadmins - Part XXXIX (InventoryPatterns) covers glob matching against inventory; CODEOWNERS globs are the same shape.

Quiz

Knowledge check · 4 questions

  1. Q1. A file at /terraform/prod/iam/main.tf is reviewed against the example CODEOWNERS. On GitHub, which owners are assigned?

  2. Q2. The CODEOWNERS pattern /terraform/ matches the file terraform/prod/main.tf.

  3. Q3. Explain the difference between terraform/*.tf and terraform/**/*.tf in CODEOWNERS.

  4. Q4. Diagnose why a CODEOWNERS rule is matching files the team did not intend to cover.

    A team writes '*.tf @platform/platform-infra' to assign every Terraform file at the repo root to platform. After deploying, they find that modules/vpc/main.tf - deep in the tree - is also assigned to platform. They wanted only root-level .tf files covered.

Passing score: 75%. Answers are checked in this browser.