Skip to content

Ownership: CODEOWNERS and service ownership

Name one owner for every path and every service; for paths, the last matching line names it.

Predict first

Before you read: predict

ExamHall's monorepo has "Require review from Code Owners" turned on for main, and the .github/CODEOWNERS file on main reads:

/packages/contracts/  @examhall/platform @examhall/grading
*                     @examhall/maintainers
/apps/proctor/        @examhall/proctoring

A pull request into main changes apps/proctor/src/flags.ts and packages/contracts/exam.ts. A team's approval means an approval from any one of its members.

Which code-owner approvals does the pull request need before it can merge?

Pick one

The 30-second version

Every path and every running service needs a named owner. A CODEOWNERS line pairs a gitignore-style pattern with owners, and for each file the last line that matches decides, so the order of the file is the policy. A service catalog entry names each service's owner and what it depends on, for the moment something breaks.

Name one owner for every path and every service; for paths, the last matching line names it.

How it works

Step 1 of 14

Code owners review their paths. A pull request that changes a file requests its code owners' review. A file no line matches has none.
In the kitchen
  • the schedule lists parts of the building, each with who must sign off work there → a CODEOWNERS line: a pattern and its owners (steps 1–2)

Analogy: A block of flats' repair schedule and the managing agent's building log

In the building In the repository
the schedule lists parts of the building, each with who must sign off work there a CODEOWNERS line: a pattern and its owners (steps 1–2)
"the whole building: the managing agent" heads it; "the lifts: the lift contractor" comes further down a catch-all first, specific lines after (step 6)
the schedule is read to the end before anyone signs off the last matching line decides (step 5)
"the roof: the building surveyor or the roofer" one owner's approval per owner set (step 8)
the bike store listed with no name: any resident with a key may let a tradesman in a line with no owner (step 7)
the agent's log: each installation's contractor, its 24-hour call-out number, its fault manual, and what it needs to run (the boiler needs the gas supply) a catalog entry: owner, on-call, runbook, dependsOn (steps 12–13)

In real terms: give every path a team in CODEOWNERS, catch-all first; give every running service a catalog entry naming its team, on-call and runbook.

Breaks down when: people argue over what a schedule meant, while GitHub applies the last matching line mechanically, even a broad line added at the bottom by mistake.

Contrasting case: a detached house whose one owner signs for every repair: with one owner, no schedule is needed.

Examples, run for real

4.1 ExamHall EXH-4330: the grading worker nobody owned

EXH-4330: ExamHall's grading worker stalled overnight. The alert named the worker but no team, and the on-call engineer could not tell who owned it. The ownership check from step 14 ran on the monorepo's files and its software catalog. MR !4630 gives the worker, the candidate app, the jobs queue and .github/ their teams below the catch-all, and adds a catalog entry for the worker, loaded with @backstage/catalog-model.

4.1 EXH-4330 before: 11 files · 8 decided only by the catch-all @examhall/maintainers, 3 of them the grading worker's · catalog: no grading-worker entry
4.1 MR !4630 after: 1 decided only by the catch-all (README.md) · catalog grading-worker: owner group:grading · on-call PagerDuty PGRADE1 · runbook "Stalled grading jobs" · depends on resource:jobs-queue (owner group:platform) · entries rejected 0

Before, every grading-worker file was decided by the catch-all line, whose team covered everything and answered for nothing (steps 5, 14). After, only README.md falls to it, and the worker's entry names its team, its paging service, its runbook and the queue it depends on (steps 12–13).

Try it

Answer first, then check. Nothing is saved.

Question 1 of 7

A ticketing company's repository has "Require review from Code Owners" on main, and its CODEOWNERS there reads:

*                 @tix/platform
venues/           @tix/venues
/Seats/           @tix/seating
/venues/seats/    @tix/inventory
venues/*          @tix/mapping

A pull request into main changes only venues/Seats/map.md.

Whose approval does it need?

Pick one

Question 2 of 7

In the ticketing company's repository, the database team has added a line for SQL files. The CODEOWNERS on main now reads:

*                    @tix/platform
/services/refunds/   @tix/payments
*.sql                @tix/dba

With "Require review from Code Owners" on, a pull request into main changes services/refunds/policy.ts, services/refunds/migrations/002.sql, services/seats/migrations/007.sql and schema.sql.

For how many of these files does code-owner review ask for @tix/dba's approval?

Pick one

Question 3 of 7

The ticketing repository has "Require review from Code Owners" on main, and its CODEOWNERS there reads:

*                     @tix/core
/services/seats/      @tix/inventory @tix/platform
/services/refunds/    @tix/payments @tix/platform
/apps/web/            @tix/web
/apps/web/static/

A pull request into main changes services/seats/hold.ts, services/refunds/rules.ts, apps/web/cart.tsx and apps/web/static/logo.svg. Priya, who is in @tix/platform and no other team, approves it.

Once she has, for how many of the four changed files does code-owner review still wait for another approval?

Pick one

Question 4 of 7

The ticketing company loads these four entries into its Backstage software catalog, checked by @backstage/catalog-model 1.10.1. One of them is rejected when the catalog loads it.

apiVersion: backstage.io/v1alpha1
kind: Component
metadata: { name: seat-hold-api, annotations: { pagerduty.com/service-id: PSEAT01 }, links: [{ url: https://runbooks.tix.example/seat-hold, title: Runbook }] }
spec: { type: service, lifecycle: production, owner: group:inventory }
---
apiVersion: backstage.io/v1alpha1
kind: Component
metadata: { name: refund-worker, annotations: { pagerduty.com/service-id: PREF01 }, links: [{ url: https://runbooks.tix.example/refunds, title: Runbook }] }
spec: { type: service, lifecycle: production, owner: user:dana.k, system: payments }
---
apiVersion: backstage.io/v1alpha1
kind: Component
metadata: { name: ticket-pdf }
spec: { type: service, lifecycle: production, owner: group:fulfilment, system: fulfilment }
---
apiVersion: backstage.io/v1alpha1
kind: Component
metadata: { name: price-sync, owner: group:pricing, annotations: { pagerduty.com/service-id: PPRICE1 }, links: [{ url: https://runbooks.tix.example/pricing, title: Runbook }] }
spec: { type: service, lifecycle: production, system: pricing }

Which one?

Pick one

Question 5 of 7

The ticketing repository has "Require review from Code Owners" on main. On main there is no .github/CODEOWNERS, but there are two other files. CODEOWNERS on main, at the root:

/services/seats/   @tix/inventory
*                  @tix/platform
/guides/           @tix/writers

and docs/CODEOWNERS on main:

*                  @tix/platform
/services/seats/   @tix/seating

A pull request from holds-v2 into main changes services/seats/hold.ts, guides/seating.md and .github/CODEOWNERS, a file it adds. .github/CODEOWNERS on holds-v2 reads:

*                  @tix/platform
/services/seats/   @tix/holds
/guides/           @tix/content

Who is asked to review it as code owner?

Pick one

Question 6 of 7

A payments-ledger repository has "Require review from Code Owners" on main, and its CODEOWNERS there reads:

/ledger/            @pay/ledger
/ledger/fixtures/
*.sql               @pay/dba
/fixtures/

A pull request into main changes ledger/post.ts, ledger/fixtures/seed.json, ledger/fixtures/seed.sql, fixtures/accounts.json and scripts/backfill.ts.

For how many of these files does code-owner review ask for a code owner's approval?

Pick one

Question 7 of 7

A chat company's Backstage catalog holds these entries, and every entry below loads without an error:

apiVersion: backstage.io/v1alpha1
kind: Component
metadata: { name: chat-gateway }
spec: { type: service, lifecycle: production, owner: realtime, dependsOn: [component:presence, resource:messages-db] }
---
apiVersion: backstage.io/v1alpha1
kind: Component
metadata: { name: presence }
spec: { type: service, lifecycle: production, owner: presence-team, dependsOn: [component:auth-api] }
---
apiVersion: backstage.io/v1alpha1
kind: Resource
metadata: { name: messages-db }
spec: { type: database, owner: storage }
---
apiVersion: backstage.io/v1alpha1
kind: Component
metadata: { name: auth-api }
spec: { type: service, lifecycle: production, owner: identity }
---
apiVersion: backstage.io/v1alpha1
kind: Component
metadata: { name: push-sender }
spec: { type: service, lifecycle: production, owner: notifications, dependsOn: [component:chat-gateway] }
---
apiVersion: backstage.io/v1alpha1
kind: Component
metadata: { name: chat-archive }
spec: { type: service, lifecycle: production, owner: archive, dependsOn: [component:chat-gateway] }

At 03:00 chat-gateway is failing and its on-call engineer suspects something it relies on: which teams own the entries chat-gateway depends on, directly or through another entry?

Pick one

Go deeper

Pro tips
  1. Put the catch-all on the first line. Write * @org/team first and the specific lines below it, so a new specific line can only narrow ownership. When: writing or reviewing a CODEOWNERS file (steps 5–6). Verify (RAN, tips.mjs): MR !4630's file, then the same lines with * moved to the end.
tip 1 catch-all on line 1: decides 1 of 11 files (README.md) · the same line moved to the end: decides 11 of 11
  1. Give the CODEOWNERS file its own owner. Add /.github/ @org/repo-admins (or /.github/CODEOWNERS), so a change to who reviews what is itself reviewed by them. When: turning on required code-owner review (step 10). Verify: "To protect a repository fully against unauthorized changes, you also need to define an owner for the CODEOWNERS file itself." Status: CITED, not run (GitHub's review service cannot run here).
Source
  • GitHub Docs, "About code owners" (the row's first source): https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners · fetched 2026-10-15 with curl. "Code owners are automatically requested for review when someone opens a pull request that modifies code that they own." (step 1); "A CODEOWNERS file uses a pattern that follows most of the same rules used in gitignore files." and "CODEOWNERS paths are case sensitive" (step 2); "@doctocat owns any files in the build/logs directory at the root of the repository and any of its subdirectories" and "@octocat owns any file in an apps directory anywhere in your repository" (step 3); "The docs/* pattern will match files like docs/getting-started.md but not further nested files like docs/build-app/troubleshooting.md." (step 4); "Order is important; the last matching pattern takes the most precedence." (step 5); "its owners are left empty. Without an owner, changes to apps/github can be made with the approval of any user who has write access to the repository." (step 7); "When reviews from code owners are required, an approval from any of the owners is sufficient to meet this requirement." (step 8); "ensure that changed code is reviewed by the owners of the changed files" (step 9); "To trigger review requests, pull requests use the version of CODEOWNERS from the base branch of the pull request." (step 10); "If CODEOWNERS files exist in more than one of those locations, GitHub will search for them in that order and use the first one it finds." (step 11); tip 2. Valid as of 2026-10-15.
  • Backstage docs, "What is Backstage?" (the row's second source): https://backstage.io/docs/overview/what-is-backstage · fetched 2026-10-15. "Backstage Software Catalog for managing all your software (microservices, libraries, data pipelines, websites, ML models, etc.)" and "it's a single, consistent experience that ties all your infrastructure tooling, resources, standards, owners, contributors, and administrators together in one place" (step 12); "Backstage is a CNCF Incubation project after graduating from Sandbox." Valid as of 2026-10-15. This page names no entry field.
  • Backstage docs, "Descriptor Format of Catalog Entities" (step 12's fields): https://backstage.io/docs/features/software-catalog/descriptor-format · fetched 2026-10-15 with curl. Under "Kind: Component": "The type of component as a string, e.g. website. This field is required.", "The lifecycle state of the component, e.g. production. This field is required." and "An entity reference to the owner of the component, e.g. artist-relations-team. This field is required."; the owner reference's kinds, "Group (default), User"; and, for the system, "An entity reference to the system that the component belongs to, e.g. artist-engagement-portal. This field is optional." (step 12). Valid as of 2026-10-15. Every graded fact about entries still comes from running @backstage/catalog-model 1.10.1.

Checked against the tools and docs as of 2026-10-15.