Scenario
Section titled “Scenario”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.
Objective
Section titled “Objective”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.
Prerequisites
Section titled “Prerequisites”- CODEOWNERS — the rules this lab exercises
- A GitHub repository with write access. Team handles like
@example-org/docs-teamneed an organisation; for a personal repository, use two user handles instead
Starting state
Section titled “Starting state”Locally, build the file layout:
mkdir -p /tmp/lab-codeowners && cd /tmp/lab-codeownersgit init -q -b main .git config user.email "lab@example.com"git config user.name "Lab User"
mkdir -p .github src/api docs infra/k8sprintf 'x\n' > docs/guide.mdprintf 'x\n' > src/app.jsprintf 'x\n' > src/api/users.jsprintf 'x\n' > infra/k8s/deploy.ymlprintf 'x\n' > README.md-
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 -
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/.File Your prediction docs/guide.mdsrc/app.jssrc/api/users.jsinfra/k8s/deploy.ymlREADME.md -
Check the prediction against the rules (answer in the Solution). One line has made every other line irrelevant. Which?
-
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. -
Predict again for the same five files with the corrected file.
-
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 maingit switch -c test-routingfor f in docs/guide.md src/app.js src/api/users.js infra/k8s/deploy.yml README.md; do echo "change" >> "$f"; donegit commit -qam "Touch every owned path"git push -u origin test-routingOpen 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.
Validation
Section titled “Validation”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.
Solution
Section titled “Solution”Step 2, with the draft file — every row is the same:
| File | Requested | Why |
|---|---|---|
docs/guide.md | @example-org/backend | Line 4 (*) matches last |
src/app.js | @example-org/backend | Line 4 |
src/api/users.js | @example-org/backend | Line 4 |
infra/k8s/deploy.yml | @example-org/backend | Line 4 |
README.md | @example-org/backend | Line 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/platformStep 5, with the corrected file:
| File | Requested | Matching 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 |
Explanation
Section titled “Explanation”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.
Troubleshooting
Section titled “Troubleshooting”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.
Clean up
Section titled “Clean up”Delete the scratch repository on GitHub if you created one, then:
cd /tmp && rm -rf lab-codeownersRelated lessons
Section titled “Related lessons”Next lab
Section titled “Next lab”Design a pull request workflow — template, checks, rules and merge method, decided together rather than one at a time.