Skip to content

Lab: Configure CODEOWNERS and Predict the Routing

Lesson 1 of 2Intermediate4 min readHands-On Git & GitHub Labs · GitHub Labs
Time20 minutes
LevelIntermediate
You needA GitHub repository you can push to, with at least one team or a second account to name as an owner

Your repository has a docs team, a backend team and a platform team. Reviews keep going to the wrong people, or to nobody. Someone added a catch-all rule “to be safe” and now the backend team is requested on every documentation change.

CODEOWNERS is fifteen lines long and everyone assumes they understand it. The rules are simple; the failure modes are silent.

Write a CODEOWNERS file, predict the routing for a set of changed files using the documented rules — before pushing — then verify on GitHub that the prediction was right. Along the way, find the three mistakes that make routing fail without any error.

  • CODEOWNERS — the rules this lab exercises
  • A GitHub repository with write access. Team handles like @example-org/docs-team need an organisation; for a personal repository, use two user handles instead

Locally, build the file layout:

Terminal window
mkdir -p /tmp/lab-codeowners && cd /tmp/lab-codeowners
git init -q -b main .
git config user.email "lab@example.com"
git config user.name "Lab User"
mkdir -p .github src/api docs infra/k8s
printf 'x\n' > docs/guide.md
printf 'x\n' > src/app.js
printf 'x\n' > src/api/users.js
printf 'x\n' > infra/k8s/deploy.yml
printf 'x\n' > README.md
  1. Write a first draft of .github/CODEOWNERS, deliberately including the common mistake:

    /docs/ @example-org/docs-team
    /src/* @example-org/backend
    /infra/** @example-org/platform
    * @example-org/backend
  2. Predict the routing. For each file, write down which owners will be requested. Use the rules: patterns follow gitignore syntax; the last matching line wins and earlier matches are discarded; a single * does not cross a /.

    FileYour prediction
    docs/guide.md
    src/app.js
    src/api/users.js
    infra/k8s/deploy.yml
    README.md
  3. Check the prediction against the rules (answer in the Solution). One line has made every other line irrelevant. Which?

  4. Fix the file. Rewrite it so that docs go to the docs team, all of src/ (including nested files) goes to backend, infra/ goes to platform, and stray markdown anywhere goes to docs.

  5. Predict again for the same five files with the corrected file.

  6. Verify on GitHub. Commit and push, then open a pull request that changes all five files:

    Terminal window
    git add . && git commit -q -m "Add CODEOWNERS and sample files"
    git remote add origin <your-repo-url>
    git push -u origin main
    git switch -c test-routing
    for f in docs/guide.md src/app.js src/api/users.js infra/k8s/deploy.yml README.md; do echo "change" >> "$f"; done
    git commit -qam "Touch every owned path"
    git push -u origin test-routing

    Open the pull request in the browser. The Reviewers panel shows who was requested and, next to each, which CODEOWNERS line matched. Compare with step 5.

Your step-5 prediction matches the Reviewers panel exactly. If any owner is missing, the Troubleshooting section lists the three reasons that happen.

Step 3. * matches every path. It is the last line. The last matching line wins. Therefore every file matches line 4 and lines 1–3 are dead.

Step 4. Put the general rule first and specific rules after it, so specific rules win. /src/* matches only files directly in src/; use /src/ (the directory) or /src/**.

Step 6. GitHub reads CODEOWNERS from the pull request’s base branch. If the file is only on test-routing, routing uses whatever main has — which is nothing.

Step 2, with the draft file — every row is the same:

FileRequestedWhy
docs/guide.md@example-org/backendLine 4 (*) matches last
src/app.js@example-org/backendLine 4
src/api/users.js@example-org/backendLine 4
infra/k8s/deploy.yml@example-org/backendLine 4
README.md@example-org/backendLine 4

Not “docs team plus backend”. Matches do not accumulate.

Step 4 — corrected file:

# General rule first; later, more specific lines override it.
* @example-org/backend
# Documentation, wherever it lives.
*.md @example-org/docs-team
/docs/ @example-org/docs-team
# Everything under src/, nested included. (/src/* would stop at one level.)
/src/ @example-org/backend
# Infrastructure.
/infra/ @example-org/platform

Step 5, with the corrected file:

FileRequestedMatching line
docs/guide.md@example-org/docs-team/docs/ (last match; *.md also matched, earlier)
src/app.js@example-org/backend/src/
src/api/users.js@example-org/backend/src/ — nested files included
infra/k8s/deploy.yml@example-org/platform/infra/
README.md@example-org/docs-team*.md

Last match wins, and only it. GitHub evaluates lines top to bottom and keeps the last one that matches. A catch-all at the bottom therefore owns everything. The idiom is general first, specific last — the opposite of how most people write lists.

/src/* stops at one level. In gitignore syntax a single * does not match /. Files in src/api/ are not matched. Name the directory (/src/) or use **.

Routing is not enforcement. CODEOWNERS decides who is requested. Whether their approval is required is a branch protection or ruleset setting (“Require review from Code Owners”). One without the other is the second most common surprise.

Read from the base branch. A contributor cannot grant themselves ownership by editing CODEOWNERS in their pull request, because routing uses the file as it exists on the branch being merged into.

An owner was not requested at all. Three silent causes, in order of likelihood: the handle is misspelled or the team no longer exists; the team or user has read-only access (owners need write access); the pull request is a draft (routing runs when it is marked ready).

Everything went to one team. A catch-all is not first. Move it to the top.

GitHub says the file has a syntax error. Settings → Code review → CODEOWNERS shows a line number. Usually a stray character in a path.

Delete the scratch repository on GitHub if you created one, then:

Terminal window
cd /tmp && rm -rf lab-codeowners

Design a pull request workflow — template, checks, rules and merge method, decided together rather than one at a time.

Choose a learning pathA sequenced route through the curriculum for wherever you are now.