Neatbo.

CODEOWNERS changed-path routing

Resolve each supplied changed path against one selected CODEOWNERS snapshot, retaining the last valid rule, all declared owners and explicit ownership gaps.

Browser-local processingInputCODEOWNERS / changed pathsOutputRouting JSON / CSVUp to 2 MiB per file · File limit: 1
  1. 1Add input
  2. 2Adjust settings
  3. 3Get your result

Tool input and files are processed in this browser without being uploaded.

Your input

Inputs are kept temporarily in this tab when switching tools. Refreshing or closing clears them; large results may need to be regenerated.

⌘ / Ctrl + Enter to run

or drag and drop it here

Files stay on this device. Your originals stay unchanged.

Up to 2 MiB per file · File limit: 1

    0 characters · 0 bytes
    Options

    Complete the required options first. You can keep the defaults for the rest.

    Preparing the tool…

    Before you start

    Group changed paths by declared reviewer without accessing GitHub. Choose the intended branch snapshot yourself; inspect skipped source lines and distinguish cleared owners from unmatched paths.

    How to use this tool

    1. Choose the intended CODEOWNERS snapshot, then enter repository-relative changed paths, one per line.
    2. Run routing; inspect skipped rule count, declared owners, explicitly unowned paths and unmatched paths.
    3. Download full JSON/CSV with source provenance and all owner groups before using the handoff.

    Supported inputs and limits

    One explicitly selected UTF-8 CODEOWNERS file or paste up to 2 MiB, plus a newline-delimited changed-path list up to 2 MiB. Choose the intended snapshot; .github/root/docs priority and pull-request base branch are not inferred.

    Up to 10,000 rule lines, 10,000 paths, 1,024 UTF-16 units per pattern/path and 100 owners per rule. Valid rule count × supplied path count must be at most 2,000,000. Complete JSON and CSV together must fit 32 MiB; combined limits apply.

    Case-sensitive GitHub-style *, ?, whole-component **, root paths and directory patterns. Last matching valid source line wins. Owners on the same line stay together; an empty owner list explicitly clears ownership. Duplicate paths remain in order. User, team and email strings are declarations; account existence, access and review requests remain unverified.

    GitHub-invalid negation, character ranges and escaped leading # lines are skipped with full original lines, reasons and UTF-8 byte offsets. Legal escaped patterns, including escaped spaces, fail the whole batch as Unsupported. Absolute/backslash/dot-dot/control/ambiguous empty path segments reject; source and path text remain inert.

    The complete report includes original sources and hashes, valid/invalid rules, every supplied path and owner grouping. Table previews 200 rows, clipping long cells to 2,000 UTF-16 units. Above 20,000 report characters, copying gives a labelled preview; full downloads retain all rows. Cancellation or an error publishes no partial routing.

    Worked example

    Example input

    # Explicitly selected CODEOWNERS snapshot
    * @general @org/reviewers
    *.js @javascript # Same-line owners replace earlier owners
    /docs/* [email protected]
    /apps/ @apps
    /apps/github
    !legacy @ignored
    
    Example options
    {"secondary":"src/app.js\ndocs/readme.md\ndocs/deep/readme.md\napps/github/action.yml\nREADME.md\nsrc/app.js\n","params":{"spreadsheetSafe":true}}

    Example output

    {"format":"GitHub-CODEOWNERS-explicit-snapshot-routing","summary":{"paths":6,"validRules":5,"invalidRules":1,"declaredOwners":4,"matchedPaths":5,"explicitlyUnowned":1,"unmatchedPaths":0,"pairWork":30,"evaluatedPairs":18,"status":"declared-routing-with-skipped-lines"},"source":{"name":"pasted.txt","bytes":202,"sha256":"15e24081c2e8ebf417920dc5806d570ea7e3b0b2289fbbe1f852bd2f27bf3f36","text":"# Explicitly selected CODEOWNERS snapshot\r\n* @general @org/reviewers\r\n*.js @javascript # Same-line owners replace earlier owners\r\n/docs/* [email protected]\r\n/apps/ @apps\r\n/apps/github\r\n!legacy @ignored\r\n"},"changedPaths":{"bytes":90,"sha256":"fdf606604efda531d80a2c4110ec1d168377aa5063c58ae073d8321dbdc2f6a3","text":"src/app.js\ndocs/readme.md\ndocs/deep/readme.md\napps/github/action.yml\nREADME.md\nsrc/app.js\n"},"rules":[{"line":2,"byteOffset":43,"raw":"* @general @org/reviewers","pattern":"*","owners":["@general","@org/reviewers"]},{"line":3,"byteOffset":70,"raw":"*.js @javascript # Same-line owners replace earlier owners","pattern":"*.js","owners":["@javascript"]},{"line":4,"byteOffset":130,"raw":"/docs/* [email protected]","pattern":"/docs/*","owners":["[email protected]"]},{"line":5,"byteOffset":156,"raw":"/apps/ @apps","pattern":"/apps/","owners":["@apps"]},{"line":6,"byteOffset":170,"raw":"/apps/github","pattern":"/apps/github","owners":[]}],"invalidRules":[{"line":7,"byteOffset":184,"raw":"!legacy @ignored","reason":"GitHub-invalid-negation"}],"routing":[{"index":1,"path":"src/app.js","inputLine":1,"line":3,"pattern":"*.js","owners":["@javascript"],"status":"declared-owners","accountPermissions":"not-verified"},{"index":2,"path":"docs/readme.md","inputLine":2,"line":4,"pattern":"/docs/*","owners":["[email protected]"],"status":"declared-owners","accountPermissions":"not-verified"},{"index":3,"path":"docs/deep/readme.md","inputLine":3,"line":2,"pattern":"*","owners":["@general","@org/reviewers"],"status":"declared-owners","accountPermissions":"not-verified"},{"index":4,"path":"apps/github/action.yml","inputLine":4,"line":6,"pattern":"/apps/github","owners":[],"status":"explicitly-unowned","accountPermissions":"not-verified"},{"index":5,"path":"README.md","inputLine":5,"line":2,"pattern":"*","owners":["@general","@org/reviewers"],"status":"declared-owners","accountPermissions":"not-verified"},{"index":6,"path":"src/app.js","inputLine":6,"line":3,"pattern":"*.js","owners":["@javascript"],"status":"declared-owners","accountPermissions":"not-verified"}],"ownerGroups":[{"owner":"@javascript","pathIndexes":[1,6]},{"owner":"[email protected]","pathIndexes":[2]},{"owner":"@general","pathIndexes":[3,5]},{"owner":"@org/reviewers","pathIndexes":[3,5]}],"scope":"Case-sensitive last valid matching line from one user-selected snapshot only. Original source includes every blank/comment/rule line; invalid rules have full original lines and byte offsets. Duplicate supplied paths remain in input order. GitHub existence/write/team visibility and actual review requests are not verified; no repository, filesystem or network access. Valid escaped patterns and unsupported legal grammar fail atomically."}

    When something does not work

    Correct path syntax or select the intended snapshot. Review every skipped rule; re-export or remove unsupported escaped grammar deliberately. Reduce rule/path combinations or full report size when a limit rejects, then rerun; no partial routing is retained.

    Frequently asked questions

    Does this tell me which reviewers GitHub will request?

    It reads declarations in the snapshot you selected. It cannot verify the pull-request base branch, account existence, write permission or team visibility; GitHub may assign no owner when those conditions fail.

    Why can a later rule with no owner remove an earlier match?

    An empty owner list is an explicit clearing rule. The report distinguishes explicitly-unowned from unmatched and retains the clearing source line.

    Does docs/* include docs/deep/readme.md?

    No. That pattern covers direct children. /docs/ covers descendants. The matcher is case-sensitive and reports the final source pattern for each supplied path.

    Documentation & further reading

    Related tools