Code ownership in a distributed team prevents the “nobody knows who’s responsible for this” problem. When a service breaks at 2am, ownership records tell the on-call engineer exactly who to page. When a PR touches a security-sensitive module, ownership rules auto-request the right reviewer.
This guide covers the tools that enforce, visualize, and maintain code ownership at scale.
GitHub CODEOWNERS (Baseline)
Every GitHub repo supports a CODEOWNERS file that auto-assigns reviewers based on file paths. It’s free, built-in, and a required foundation before adding heavier tooling.
.github/CODEOWNERS
Global fallback owner
* @your-org/platform-team
Backend services
/services/auth/ @alice @bob
/services/payments/ @payments-team
/services/notifications/ @backend-team
Infrastructure
/infra/ @devops-team
/infra/terraform/ @alice @charlie
Frontend
/frontend/ @frontend-team
/frontend/src/components/ @design-system-team
Shared libraries. require two approvals
/libs/ @your-org/senior-engineers
Security-sensitive files
/config/secrets.yml @security-team
/.github/workflows/ @devops-team @security-team
Enforce that CODEOWNERS reviews are not bypassed via branch protection:
Settings > Branches > Branch protection rules > Require review from Code Owners
Check for ownership gaps:
List files with no CODEOWNERS match
git ls-files | while read f; do
owner=$(cat .github/CODEOWNERS | awk -v file="$f" '
$1 != "#" && file ~ gensub(/\*/, ".*", "g", $1) { owner=$2 }
END { print owner }
')
[ -z "$owner" ] && echo "NO OWNER: $f"
done
Backstage Software Catalog (Team-Scale)
Backstage adds a service catalog that maps ownership at the component and system level, beyond what CODEOWNERS covers.
Install the catalog plugin:
In your Backstage app directory
yarn --cwd packages/app add @backstage/plugin-catalog
yarn --cwd packages/backend add @backstage/plugin-catalog-backend
Define a component with ownership metadata:
catalog-info.yaml (in each service repo)
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: payments-service
description: Handles all payment processing
annotations:
github.com/project-slug: your-org/payments-service
pagerduty.com/service-id: P1234AB
spec:
type: service
lifecycle: production
owner: payments-team
system: financial-platform
dependsOn:
- component:auth-service
- resource:payments-db
Register the catalog entry in app-config.yaml:
catalog:
rules:
- allow: [Component, System, API, Group, User, Resource, Location]
locations:
- type: github-discovery
target: https://github.com/your-org/*/blob/main/catalog-info.yaml
The Backstage UI then shows every service, its owner team, its dependencies, and links to runbooks, PagerDuty schedules, and CI pipelines. all in one place.
Sourcegraph Own (Large Codebases)
For monorepos or organizations with hundreds of repos, Sourcegraph’s own feature gives codebase-wide ownership search.
Enable in sourcegraph.yaml:
experimentalFeatures:
ownStats: true
Sourcegraph supports both GitHub CODEOWNERS files and its own signal-based ownership inference (based on who last modified code). Query ownership:
Find all files owned by the payments team
file:contains.owner(@payments-team)
Find files with no owner
-file:has.owner()
Find owner of a specific file
repo:^github\.com/your-org/payments-service$ file:^src/charge.go select:file.owners
The ownership API is also scriptable for generating reports:
curl -s \
-H "Authorization: token $SOURCEGRAPH_TOKEN" \
"https://sourcegraph.yourcompany.com/.api/graphql" \
-d '{"query": "{ search(query: \"-file:has.owner()\", version: V3) { results { matchCount } } }"}' \
| jq '.data.search.results.matchCount'
OpsLevel for Service Maturity
OpsLevel layers ownership into a broader service maturity framework. useful when you want to enforce not just “who owns this” but “does the owner maintain it to a standard.”
Connect your GitHub org:
OpsLevel CLI
npm install -g @opslevel/cli
opslevel configure --api-token $OPSLEVEL_API_TOKEN
Define service ownership via YAML config:
opslevel.yml (in each service repo)
version: 1
service:
name: Payments Service
product: Financial Platform
tier: tier_1
lifecycle: production
owner: payments-team
language: Go
framework: gRPC
repos:
- https://github.com/your-org/payments-service
tags:
- env:production
- pci:yes
Push service config:
opslevel services create --from-file opslevel.yml
OpsLevel then scores each service against your rubric (does it have an owner, runbook, on-call schedule, passing checks?). Teams see their score and what’s missing.
Automated Ownership Audits
Run weekly audits to catch drift:
#!/bin/bash
scripts/ownership-audit.sh
Reports files with no CODEOWNERS entry
set -euo pipefail
REPO_ROOT=$(git rev-parse --show-toplevel)
CODEOWNERS="$REPO_ROOT/.github/CODEOWNERS"
UNOWNED_FILES=()
while IFS= read -r file; do
# Skip generated files and vendor dirs
[[ "$file" == vendor/* ]] && continue
[[ "$file" == *.lock ]] && continue
matched=false
while IFS= read -r line; do
[[ "$line" =~ ^# ]] && continue
[[ -z "$line" ]] && continue
pattern=$(echo "$line" | awk '{print $1}')
if [[ "$file" == $pattern* ]] || [[ "$file" == *$pattern* ]]; then
matched=true
break
fi
done < "$CODEOWNERS"
$matched || UNOWNED_FILES+=("$file")
done < <(git ls-files)
if [ ${#UNOWNED_FILES[@]} -gt 0 ]; then
echo "=== UNOWNED FILES ==="
printf '%s\n' "${UNOWNED_FILES[@]}"
echo ""
echo "Total unowned: ${#UNOWNED_FILES[@]}"
exit 1
fi
echo "All files have owners."
Add it to CI:
.github/workflows/ownership-audit.yml
name: Ownership Audit
on:
push:
paths:
- '.github/CODEOWNERS'
- '/*.go'
- '/*.ts'
schedule:
- cron: '0 9 * * 1' # Every Monday morning
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check code ownership
run: bash scripts/ownership-audit.sh
CODEOWNERS Linting
Validate your CODEOWNERS file doesn’t reference deleted teams or invalid paths:
Install codeowners validator
go install github.com/mszostok/codeowners-validator@latest
Run validation
codeowners-validator \
--repository-path=. \
--owner-checker-token=$GITHUB_TOKEN \
--checks="files,syntax,owners,duppatterns"
Common errors it catches:
- Pattern matches no files
- Team doesn’t exist in GitHub org
- Duplicate patterns (later entry silently wins)
- Syntax errors in path patterns
Tool Comparison
| Tool | Scope | Cost | Best For |
|---|---|---|---|
| GitHub CODEOWNERS | Per-repo, file-level | Free | All teams, baseline |
| Backstage Catalog | Cross-repo, service-level | Free (self-hosted) | 50+ services |
| Sourcegraph Own | Monorepo, file-level search | Paid | Large eng orgs |
| OpsLevel | Service maturity + ownership | Paid | Compliance-heavy teams |
Start with CODEOWNERS in every repo. Add Backstage when you have more services than people can track in their heads. Layer in OpsLevel or Sourcegraph when audits and compliance become a concern.
Related Reading
- Remote Team Git Hooks Standardization Guide
- Remote Team Code Review Checklist Template
- How to Set Up Woodpecker CI for Self-Hosted
- Async Code Review Process Without Zoom Calls Step by Step
Related Articles
- Remote Code Review Tools Comparison 2026
- CI/CD Pipeline Tools for a Remote Team of 2 Backend
- Best Practice for Remote Team Code Review Comments
- List all markdown files in your docs directory
- Remote Developer Code Review Workflow Tools for Teams
Built by theluckystrike. More at zovo.one