CODEOWNERS Shadow Checker
GitHub resolves a CODEOWNERS file with the last matching pattern wins --
the same rule .gitignore uses. That means a narrow, specific rule can work perfectly for
months and then silently stop winning the moment anyone adds a broader rule that also matches, anywhere
below it in the file. Nothing errors. The pull requests it used to route for review just quietly stop
requiring that reviewer. This tool resolves every file in your list against every rule, the same way
GitHub does, and names exactly which rules are dead weight.
1. Get your CODEOWNERS file and file list (optional CLI)
Node.js, no dependencies to install. Auto-detects CODEOWNERS, .github/CODEOWNERS,
or docs/CODEOWNERS, and your tracked files via git ls-files:
node cli/check.mjs
Get check.mjs and codeowners.mjs from the repo — plain Node, nothing to install, nothing sent anywhere. You do not need the CLI at all: paste your CODEOWNERS file and a plain list of paths directly into step 2 below instead.
2. Paste your CODEOWNERS file and file list
Paste your CODEOWNERS file and file list, then click Check.
Report
Rules
| # | Pattern | Owners |
|---|
Files with no matching rule
| Path |
|---|
Common questions
What does "last match wins" actually mean?
GitHub checks a file's path against every pattern in CODEOWNERS, in file order, and the LAST pattern that matches decides who owns the file -- not the most specific one, and not the first one. This is the same rule .gitignore uses. A narrow rule near the top of the file is silently overridden the moment a broader rule that also matches gets added anywhere below it, however much later.
Why does a broad rule added later break a specific rule that already worked?
The specific rule still exists and still matches its files -- it just stops winning, because the new broad rule matches those same files and sits after it in the list. Nothing errors, nothing warns, and the pull requests it used to route for review just quietly stop requiring that reviewer. This is the single most common way a CODEOWNERS file drifts from what its author intended.
How is this different from GitHub's own CODEOWNERS syntax highlighting?
GitHub's editor flags syntax errors -- an unknown user, a malformed pattern. It does not check whether a rule ever actually wins ownership of a real file in your repository, because that requires resolving every pattern against every path and comparing winners, which is exactly what this tool does against the file list you provide.
A rule shows as "unmatched" instead of "shadowed" -- what is the difference?
Shadowed means the rule matched files in your list but a later rule always overrode it -- real evidence the rule is dead weight. Unmatched means the rule matched nothing in your list at all, which usually means a typo in the pattern, a path that no longer exists, or simply a sample that did not happen to include a matching file -- so it is reported separately rather than lumped in with genuine shadowing.
Does a clean report mean my CODEOWNERS file is correct?
It means no rule in your file is dead weight against the files you listed, every owner is syntactically valid, and every listed file has a matching rule (or is flagged as uncovered). It cannot tell you an owner is a real collaborator on the repository -- that needs an authenticated GitHub API call this tool deliberately never makes -- and it can only see the files you gave it, so a file you left out of the list is not checked.