Keeping a Monorepo Sane: CODEOWNERS and Path-Scoped Checks
A large monorepo only stays usable with clear ownership. Use CODEOWNERS routing and path-scoped CI to keep review targeted and merges stay fast.
Netbay Developer Relations
Netbay Engineering
On this page
A monorepo starts with wonderful economics and ends with a review bottleneck: every change touches shared history, so by default every change asks the whole team to review it. The escape hatch is explicit ownership. If every path has exactly one answer to "whose review does this need," then review volume collapses to what actually changed, and path-scoped checks stop wasting CI minutes on directories that moved not at all.
CODEOWNERS: last matching pattern wins
The .github/CODEOWNERS file maps path patterns to owners, and the rule to remember is that the last matching line wins. Patterns are gitignore-style, one per line, and owners can be usernames or teams — teams are the durable choice. When a pull request touches a path with a matching owner, GitHub automatically requests a review from that owner.
# .github/CODEOWNERS — order matters, last match wins
* @netbay/platform-core
**/*.md @netbay/docs-writers
cmd/api/** @netbay/backend
internal/gateway/** @netbay/backend
web/** @netbay/frontend
lib/auth/** @netbay/security-review
.github/CODEOWNERS @netbay/platform-coreThree details do the real work here. First, the "*" fallback caches you: any path nobody claimed still lands with the core team instead of vanishing. Second, ordering — "lib/auth/**" is listed after "cmd/api/**" so an auth file under cmd still matches the more specific rule placed later. Third, a file that owns itself: changes to CODEOWNERS are reviewed by the core team, so the ownership map cannot be silently rewritten by whoever happens to be editing.
Requiring owner review
CODEOWNERS routing requests reviews but does not force them. To make ownership a gate, pair it with branch protection or a ruleset: when "require_code_owner_review" is on, a PR touching an owned path cannot merge until someone from the matched owner approves. The sharp edge of this feature: if a PR touches several owned paths, every matched owner must approve. That sounds like friction and is — deliberately. The corrective is to keep ownership borders honest, so a "web" change does not accidentally drag backend rules in because of one misplaced shared file. When it does happen, the fix is usually real: the shared path should be owned by whoever truly owns it, or the rule list should gain an explicit exemption line, rather than the boundary being ignored and the gate quietly made toothless.
Path-scoped checks: run what the diff needs
In a large tree, running the full pipeline for a one-file documentation change is waste that gets slower every week. A paths-filter step decides which jobs the diff actually engages:
# .github/workflows/ci.yml — filter once, run only what changed
name: ci
on:
pull_request:
jobs:
changes:
runs-on: ubuntu-latest
outputs:
api: ${{ steps.filter.outputs.api }}
docs: ${{ steps.filter.outputs.docs }}
steps:
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
api:
- 'cmd/api/**'
docs:
- 'docs/**'
api:
needs: changes
if: needs.changes.outputs.api == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: go test ./cmd/api/...
docs:
needs: changes
if: needs.changes.outputs.docs == 'true'
runs-on: ubuntu-latest
steps:
- run: npx markdownlint-cli2 "docs/**/*.md"The branch protection lesson from before applies: the check names here — "api", "docs" — are exactly what you require in your status check list, so when a job is legitimately skipped it must *report* success rather than disappear. That is what makes required checks and conditional jobs coexist.
Ownership debt is real
Ownership maps decay the way every map does — slowly, until someone notices a directory nobody owns. Two preventives. Own by team, not by individual; the person leaves, the team does not. And make unowned paths visible instead of tolerated. A cheap churn scan tells you where ownership is genuinely needed:
# where has the most churn been this year? that is where ownership matters
git log --since=2026-01-01 --name-only --pretty=format: | sort | uniq -c | sort -rn | head -40
# confirm which top-level paths no CODEOWNERS line claims
grep -E '^[^# ]' .github/CODEOWNERS | awk '{print $1}'Right-sized ownership turns the review queue from "everyone reviews everything" into "the person who owes this code wakes up." It also makes onboarding honest: a new hire asks one question — who owns what — instead of acquiring a folk map. Make unowned paths a review finding: when a pull request adds a directory with no matching owners line, the review should ask which team owns it before the merge, so the map grows deliberately instead of decaying. The same discipline applies to checks: a machine-skipped status must still be reported to the protection gate, because a check that silently disappears is worse than a failing one you can see.
Takeaway: CODEOWNERS gives every path one accountable owner, requiring owner review makes that accountability binding, and path-scoped checks spend CI time only where the diff went. A monorepo run this way keeps its datas, and the review and tooling boxes behind it run great on a Lucknow DC01 VPS from Netbay, live in under 60 seconds at netbayhosts.in.
Keep reading
Follow along on a real VPS
Deploy Linux in under 60 seconds
These guides are written against Ubuntu, Debian, and RHEL-family images — the same ones on NetBay.
Deploy an instance