← CODEOWNERS Shadow Checker

This checker vs. reading CODEOWNERS by eye

Reading the file yourself works fine for a short CODEOWNERS file with a handful of rules -- you can usually tell which pattern is more specific at a glance. It stops working the way most CODEOWNERS files actually grow: a specific rule (/src/payments/*.js @payments-team) is added, works for months, and then someone adds a broader rule further down the file (*.js @generalists) for an unrelated reason. Nothing about that is visible from reading top to bottom. GitHub's own syntax highlighting finds no problem either -- every pattern is valid, every owner is real. The earlier rule still matches its files; it just never wins ownership of them again, silently, under GitHub's last-match-wins resolution.

This tool does not read the file for you -- it resolves it the way GitHub actually does, every pattern against every file in your real list, and names exactly which rules never win anything because a later, broader rule always overrides them. That is a mechanical resolution problem, not a reading-comprehension one, and it is the one thing GitHub's own syntax checker does not do.

QuestionWhere to answer it
I just want to confirm my CODEOWNERS file has no syntax errors. GitHub's own editor highlighting -- that's exactly its job, and this tool checks the same syntax rule as a secondary pass.
I want to know whether an earlier, specific rule still wins ownership against my actual files, or a later rule quietly overrides it. This checker -- resolves last-match-wins against your real file list and names the exact overriding rule, instead of leaving you to trace it by eye.
I want this checked automatically in CI, with a non-zero exit on a shadowed rule. This checker's exported buildReport function -- wireable into any CI step, no GitHub API call required.
I'd rather not re-derive last-match-wins by hand every time the file changes. This checker -- paste the CLI's JSON (or the file contents and path list) into the report viewer and get the resolution done for you.

Neither replaces the other, and in practice both matter: GitHub's own highlighting for whether the file parses at all, this tool for the recurring question reading it once never answers -- whether every rule you think is protecting a path is actually still winning anything.

Try it

node cli/check.mjs

Auto-detects your CODEOWNERS file and tracked files, no reading required. Paste the JSON into the checker for the plain-English report of exactly which rules a later rule shadows.

Common questions

Should I use this tool instead of just reading the CODEOWNERS file myself?

For a short file with a handful of rules, reading it yourself is often enough -- you can usually see which pattern is more specific at a glance. This tool exists for the case that breaks that instinct: a file that has grown over months or years, where a broad rule added recently for an unrelated reason now sits below several older, specific rules and silently wins every one of them under GitHub's last-match-wins order. Nothing about that is visible from reading the file top to bottom -- it only shows up once you resolve every pattern against every file the way GitHub actually does.

Isn't this the same thing GitHub's own CODEOWNERS syntax highlighting already checks?

No, and that gap is exactly why this tool exists. GitHub's editor confirms every pattern parses and every owner is a real, well-formed reference -- it does not resolve each pattern against your actual file list and compare who wins. A CODEOWNERS file can be syntactically perfect, with GitHub's own highlighting showing no problem anywhere, while a later catch-all rule has already made an earlier, specific rule dead weight. Syntax validity and resolution correctness are two different questions, and GitHub's built-in check only answers the first.

How is this different from just grepping the file for a pattern I'm worried about?

Grepping finds the rule; it doesn't tell you whether the rule still wins anything. You'd still have to manually check every later line for a broader pattern that could override it, against every file that rule is supposed to cover -- exactly the resolution this tool automates. It reports every rule that never wins ownership of any file in your list, separately from rules that matched nothing at all, so you don't have to re-derive last-match-wins by hand for each one you're unsure about.

Is my CODEOWNERS file or file list uploaded anywhere?

No. The CLI (check.mjs) reads files already on your machine and never makes a network request -- it does not need to, since resolving CODEOWNERS needs no GitHub API call. The browser report viewer only parses text you paste into it yourself, locally in your browser. The only thing stored is an anonymous local count of which features were used and the day, with no CODEOWNERS content and no file paths in it.

Want the exact before-and-after of a rule losing its review requirement? Read why a CODEOWNERS review requirement can silently stop firing.