Forgejo CODEOWNERS and Protected Branches: Version Your Review Routing
Move 'who reviews this?' from Slack to your repo. Forgejo reads CODEOWNERS to auto-request reviewers, and branch protection turns that routing into a merge requirement.
21 Jun 2026, 11:49 UTC

The problem: review routing lives in people's heads
In a shared repo, the answer to "who must look at this?" is often tribal knowledge. An infra change ships because the author forgot to ping the platform team; a security-sensitive fix merges because the on-call engineer was on vacation. The mapping from file paths to responsible humans exists in Slack threads and old PR comments, not in version control. That makes it invisible to new contributors and impossible to audit during an incident.
The thesis: encode ownership in the repository
Forgejo supports CODEOWNERS, a file that maps path patterns to owners. Put it at the repo root, and Forgejo will automatically request the listed owners when someone opens a pull request touching those paths. Combine that with branch protection on your default branch, and the routing stops being a suggestion: it becomes a condition for merge. The ownership rules are versioned with the code, reviewable in git history, and visible to everyone.
How CODEOWNERS matching works
A CODEOWNERS file uses simple lines: a path pattern, then one or more owners. Owners can be individual users (@username), teams (@org/teamname), or email addresses. Forgejo evaluates patterns top to bottom, and later matches override earlier ones. That precedence rule matters: put broad defaults first and specific paths last.
Forgejo commonly reads CODEOWNERS from the repository root. Some builds also check a docs/ directory or a dotfile config location, but the root file is the reliable place to start. Because support details vary across Forgejo releases, treat GitHub's CODEOWNERS documentation as a guide, not a spec—verify behavior in your own instance.
Making routing enforceable with branch protection
CODEOWNERS alone is advisory. It adds reviewers, but anyone with write access can still merge a pull request without waiting for them. To close that gap, enable branch protection on your default branch in Repository Settings → Branches. The typical controls are:
- Require pull requests before merging (blocks direct pushes).
- Set a minimum approval count (for example, one or two approvals).
- Where your Forgejo version offers it, require that at least one approval comes from a code owner.
- Optionally require status checks to pass so CI gates the merge.
The exact labels and toggles differ between Forgejo versions and may not match GitHub one-for-one. Record what your instance exposes before relying on a specific rule.
Worked example: a small service repository
Imagine a repository with infrastructure manifests, a payments service, and documentation. You want the platform team to see everything by default, security to review infra changes, the payments on-call team to review service code, and the docs team to review documentation.
* @acme/platform
/infra/ @acme/platform @acme/security
/services/payments/ @acme/payments-oncall
/docs/ @acme/docs
When a contributor opens a pull request that modifies both services/payments/main.go and infra/terraform.tf, Forgejo matches the changed files against the patterns. Because /services/payments/ and /infra/ are more specific than the default * rule, Forgejo requests @acme/payments-oncall, @acme/platform, and @acme/security. The @acme/docs team is not requested because no docs files changed.
If branch protection requires one approval, any of those reviewers can unblock the merge. If it requires two approvals with at least one from a code owner, then @acme/platform or @acme/security must approve before the branch can merge, depending on which code-owner option your build supports.
Trade-offs and limitations
This setup is process, not a security boundary. Repository admins and users with explicit merge-bypass permissions can still land changes without satisfying the approval rules. CODEOWNERS matching also depends on paths and team membership staying current: if you rename @acme/security to @acme/appsec, review requests silently stop until the file is updated. Prefer teams over individual handles so vacations and role changes do not break routing.
Finally, do not assume GitHub parity. The code-owner approval requirement, dismissal rules when new commits are pushed, and admin override behavior are areas where Forgejo's implementation may differ. Check your version's release notes or admin documentation before treating a toggle as guaranteed.
Actionable close
Start small. In a scratch repository, add a root CODEOWNERS file with a single default rule (* @yourteam), then add two path rules for your riskiest directories. Open a pull request touching each path and confirm the expected reviewers appear. Next, enable branch protection on the default branch and note exactly which approval and status-check options your Forgejo build presents. Review the CODEOWNERS diff during your next incident retro—if a change reached production without the right eyes, the fix probably belongs in that file.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.