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