Language server for CODEOWNERS files with diagnostics, navigation, and code actions. Also includes a standalone CLI for linting.
# Lint CODEOWNERS (auto-detects location)
codeowners-cli lint
codeowners-cli lint --json # Machine-readable output for CI
codeowners-cli lint --fix # Auto-fix safe issues (dupes, shadowed, no-match)
codeowners-cli lint --strict # Exit non-zero on warnings (not just errors)
codeowners-cli lint --github-actions # Output GitHub Actions annotations for PR feedback
# Check who owns a file
codeowners-cli check src/main.rs
# Check multiple files (positional or --files, consistent with coverage)
codeowners-cli check src/main.rs src/lib.rs
codeowners-cli check --files src/main.rs src/lib.rs
# JSON output (for CI/scripting)
codeowners-cli check --json src/main.rs
# Check files from a list (useful for GitHub Actions)
codeowners-cli check --json --files-from changed_files.txt
git diff --name-only origin/main | codeowners-cli check --json --stdin
# Show coverage stats (exits non-zero if uncovered files exist)
codeowners-cli coverage
codeowners-cli coverage --json # JSON output for CI
codeowners-cli coverage --tree # Show unowned files as directory tree
# Check coverage for specific files (useful for CI on PRs)
codeowners-cli coverage --files src/new_file.rs src/other.rs
codeowners-cli coverage --files-from changed_files.txt
git diff --name-only origin/main | codeowners-cli coverage --stdin
# Suggest owners for unowned files (requires lookup_cmd config)
# NOTE: Experimental - requires lookup_cmd to resolve emails to teams
codeowners-cli suggest # Preview suggestions
codeowners-cli suggest --write # Add suggestions to CODEOWNERS
codeowners-cli suggest --format codeowners # Ready-to-paste CODEOWNERS lines
codeowners-cli suggest --min-confidence 50 # Higher confidence threshold
codeowners-cli suggest --anchored # Use /path instead of path
# Optimize CODEOWNERS patterns (shadowed rules, no-match, consolidation)
codeowners-cli optimize # Preview optimizations
codeowners-cli optimize --write # Apply optimizations to file
codeowners-cli optimize --json # JSON output for CI
codeowners-cli optimize --min-files 5 # Require 5+ files for dir consolidation
# Format CODEOWNERS file
codeowners-cli fmt # Prints formatted output
codeowners-cli fmt --write # Writes in place
# Validate owners against GitHub API
codeowners-cli validate-owners # Uses GITHUB_TOKEN env var
codeowners-cli validate-owners --json # JSON output for CI
codeowners-cli validate-owners --token ghp_xxx
# Validate only owners relevant to specific files (useful for CI on PRs)
codeowners-cli validate-owners --files src/new.rs src/other.rs
codeowners-cli validate-owners --files-from changed_files.txt
git diff --name-only origin/main | codeowners-cli validate-owners --stdin
# Show all files color-coded by owner
codeowners-cli tree
# Show config file paths and merged settings
codeowners-cli config
# Generate shell completions
codeowners-cli completions zsh # zsh, bash, fish, powershell, elvish
# GitHub Actions all-in-one command
codeowners-cli gha --changed-files-from changed.txt
# Runs: coverage (changed + all), owner validation (changed + all), lint
# Outputs: JSON to stdout, GITHUB_OUTPUT vars, GITHUB_STEP_SUMMARY markdown
# Fails on: uncovered changed files OR invalid owners for changed filesThe gha command runs all CODEOWNERS checks in one efficient call with native GitHub Actions integration.
name: CODEOWNERS
on:
pull_request:
branches: [main]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install codeowners-cli
run: |
gh release download --repo radiosilence/codeowners-lsp \
--pattern '*x86_64-unknown-linux-musl*' --output - | tar xz -C /usr/local/bin
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Get changed files
run: |
gh api "repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/files" \
--paginate --jq '.[].filename' > changed_files.txt
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: CODEOWNERS check
run: codeowners-cli gha --changed-files-from changed_files.txt
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}| Check | Scope | Failure Mode |
|---|---|---|
| Coverage | Changed files | Fails if any lack owners |
| Coverage | All files | Warns only |
| Owner validation | Changed files | Fails if owners invalid/missing |
| Owner validation | All files | Warns only |
| Lint | CODEOWNERS file | Annotations on lines |
The command produces:
- Human-readable summary - Concise terminal output showing check results
- Annotations -
::error::and::warning::messages appear inline on PR diffs - Step Summary - Markdown report in the Actions UI with tables and status
- Output Variables - For use in subsequent workflow steps:
has-coverage-issues/coverage-issueshas-dead-entries/dead-entrieshas-invalid-teams/invalid-teams
Skip checks:
--no-coverage-changed- Don't fail on uncovered changed files--no-coverage-all- Don't warn about all uncovered files--no-owners-changed- Don't fail on invalid owners for changed files--no-owners-all- Don't warn about all invalid owners--no-lint- Skip lint checks
Control output:
--no-annotations- Suppress::error::/::warning::messages--no-summary- Don't write step summary--no-outputs- Don't write output variables
Criterion benchmarks cover parsing, pattern matching, diagnostics, file cache, and handler functions against synthetic data (1000 rules, 50k files).
cargo bench # full suite
cargo bench --bench parsing # single group
./scripts/bench-summary.sh # summary table from last runGroups: parsing, pattern_matching, diagnostics, file_cache, lsp_handlers
- Hover: Shows file ownership with clickable GitHub links and rich metadata (team descriptions, member counts, user bios)
- Inlay Hints: Displays ownership at the top of each file
- Code Actions: Take ownership of files directly from your editor
- Diagnostics:
- Invalid glob patterns
- Invalid owner format (@user, @org/team, or email)
- Patterns matching no files
- Duplicate/shadowed rules (dead code)
- Rules with no owners
- Coverage: count of files without owners
- Completions:
- fzf-style fuzzy path completions (e.g.,
s/mmatchessrc/main.rs) - Snippet completions with owner placeholders (e.g.,
src/** @...→ cursor on@owner) - Owner completions from GitHub API with background validation (trigger:
@)
- fzf-style fuzzy path completions (e.g.,
- Inlay Hints: Shows how many files each pattern matches (hover for markdown tooltip)
- Code Lens: Inline file count and owners above each rule
- Document Symbols: Outline view with sections and rules (Cmd+Shift+O)
- Workspace Symbols: Search patterns and owners across file (Cmd+T)
- Folding: Collapse comment blocks and sections
- Semantic Highlighting: Syntax colors for patterns, owners, globs, comments
- Find References: Find all rules containing an owner
- Rename: Rename an owner across all rules
- Signature Help: Pattern syntax documentation while typing (
*,**,?,/) - Selection Range: Smart expand selection (word → owner → all owners → rule → section)
- Pattern Hover: Hover over patterns to see matching files
- Code Actions:
- Remove shadowed rules
- Remove duplicate owners
- Add owner to empty rules
- Add catch-all rule for unowned files
- GitHub Validation (optional): Validates users/teams exist on GitHub
All heavy operations (file scanning, pattern matching, GitHub API calls) run in background threads—the LSP never blocks your editor.
mise use -g github:radiosilence/codeowners-lsp@latestDownload the latest release from Releases.
codeowners-lsp # Communicates over stdioUse the codeowners-zed extension (handles installation automatically).
The LSP finds CODEOWNERS in standard locations: .github/CODEOWNERS, CODEOWNERS, docs/CODEOWNERS.
Create .codeowners-lsp.toml in your workspace root. For user-specific overrides (gitignore this), use .codeowners-lsp.local.toml.
# CODEOWNERS location (relative to workspace root)
path = "custom/CODEOWNERS"
# Your identifiers for "take ownership" actions
individual = "@username"
team = "@org/team-name"
# GitHub validation (optional)
github_token = "env:GITHUB_TOKEN"
validate_owners = false
# Suggest command settings
[suggest]
# Resolve git emails to team names (required for suggest to work)
lookup_cmd = "your-tool lookup {email} | jq -r .team"
# Prepend / to paths (anchored patterns)
anchored = true
# Diagnostic severity overrides
# Values: "off", "hint", "info", "warning", "error"
[diagnostics]
invalid-pattern = "error" # default: error
invalid-owner = "error" # default: error
pattern-no-match = "warning" # default: warning
duplicate-owner = "warning" # default: warning
shadowed-rule = "warning" # default: warning
no-owners = "off" # default: hint
github-owner-not-found = "warning" # default: warning
github-owner-unverified = "hint" # default: hintJSON settings can also be passed via LSP init options (these override TOML config):
{
"path": "custom/CODEOWNERS",
"individual": "@username",
"team": "@org/team-name",
"github_token": "env:GITHUB_TOKEN",
"validate_owners": false,
"diagnostics": {
"no-owners": "off"
}
}| Option | Description |
|---|---|
path |
Custom CODEOWNERS location (relative to workspace root) |
individual |
Your GitHub handle for "take ownership" actions |
team |
Your team's handle for "take ownership" actions |
github_token |
GitHub token for owner validation. Use env:VAR_NAME to read from environment |
validate_owners |
Enable GitHub API validation of @user and @org/team (default: false) |
[suggest] |
Settings for the suggest command |
suggest.lookup_cmd |
Command to resolve git emails to teams (use {email} placeholder) |
suggest.anchored |
Prepend / to paths for anchored patterns (default: false) |
[diagnostics] |
Map of diagnostic code to severity override |
| Code | Default | Description |
|---|---|---|
invalid-pattern |
error | Pattern has invalid glob syntax |
invalid-owner |
error | Owner format invalid (must be @user, @org/team, or email) |
pattern-no-match |
warning | Pattern doesn't match any files in the repository |
duplicate-owner |
warning | Same owner listed multiple times on one rule |
shadowed-rule |
warning | Rule is shadowed by a later rule (dead code, last match wins) |
no-owners |
hint | Rule has a pattern but no owners assigned |
file-not-owned |
error | File has no matching CODEOWNERS rule (shown on non-CODEOWNERS files) |
github-owner-not-found |
warning | Owner not found on GitHub (requires validate_owners = true) |
github-owner-unverified |
hint | Owner couldn't be verified (token may lack read:org scope) |
Override severities in config with: off, hint, info, warning, error
| Feature | Status |
|---|---|
| Hover (ownership info) | ✅ |
| Inlay hints (ownership) | ✅ |
| Go-to-definition | ➖ removed |
| Take ownership actions | ✅ |
| Diagnostics (invalid patterns/owners) | ✅ |
| Diagnostics (no matching files) | ✅ |
| Diagnostics (shadowed rules) | ✅ |
| Diagnostics (duplicate owners) | ✅ |
| Diagnostics (no owners) | ✅ |
| Coverage reporting | ✅ |
| Path completions | ✅ |
| Snippet completions (with owner placeholder) | ✅ |
| Owner completions (GitHub API) | ✅ |
| GitHub owner validation | ✅ |
| CLI: lint | ✅ |
| CLI: check | ✅ |
| CLI: coverage | ✅ |
| CLI: fmt | ✅ |
| Code actions: remove shadowed | ✅ |
| Code actions: remove duplicate owners | ✅ |
| Code actions: add owner | ✅ |
| Code actions: add catch-all | ✅ |
| CLI: lint --fix (auto-fix safe issues) | ✅ |
| LSP: textDocument/formatting | ✅ |
| Hover: clickable GitHub links | ✅ |
| Code actions: fix all safe issues | ✅ |
| Configurable diagnostic severities | ✅ |
| Hover: link to CODEOWNERS rule | ✅ |
| CLI: validate-owners | ✅ |
| CLI: tree (color-coded by owner) | ✅ |
| CLI: shell completions | ✅ |
| Hover: rich team/user metadata | ✅ |
| fzf-style fuzzy path completion | ✅ |
| Background GitHub validation | ✅ |
| Real-time pattern validation | ✅ |
| Document symbols (outline view) | ✅ |
| Workspace symbols (search) | ✅ |
| Folding ranges | ✅ |
| Semantic tokens (syntax highlighting) | ✅ |
| Find all references | ✅ |
| Rename symbol | ✅ |
| Code lens | ✅ |
| Signature help (pattern syntax) | ✅ |
| Selection range (smart expand) | ✅ |
| Linked editing (multi-cursor owners) | ➖ removed |
| Pattern hover (show matches) | ✅ |
| Related diagnostics (shadowed links) | ✅ |
| CLI: suggest (git-based suggestions) | |
| CLI: optimize (pattern consolidation) | ✅ |
➖ Go-to-definition and linked editing were removed after shipping: the first hijacked click-to-navigate in every file, the second edited every occurrence of an owner when you meant one. Hover and rename (F2) cover the same ground.
The optimizer detects two types of issues:
1. Shadowed Rules (Dead Code)
CODEOWNERS uses "last match wins" semantics. If a later rule matches the same files as an earlier one, the earlier rule is dead code:
/src/auth/ @security # ❌ Dead - shadowed by /src/ below
/src/ @backend # ✅ This wins for /src/auth/*
The optimizer works backwards from the end of the file, tracking which patterns could shadow earlier ones. A pattern is shadowed if any later pattern "subsumes" it (matches everything it matches). The catch-all * subsumes everything, so any rule before a final * @team is dead.
Key subsumption rules:
*and**subsume all patterns/src/subsumes/src/lib/(parent directory contains child)docs/(unanchored) subsumes/docs/(anchored) - unanchored matches more/docs/does NOT subsumedocs/- anchored matches fewer paths
2. Directory Consolidation
When multiple files in a directory have identical owners:
/src/lib/foo.rs @team # These three lines...
/src/lib/bar.rs @team
/src/lib/baz.rs @team
...can become:
/src/lib/ @team # ...this one line
Consolidation only triggers when:
- All files in the directory are explicitly listed
- All have exactly the same owners
- At least 3 files (configurable with
--min-files) - The resulting pattern wouldn't be immediately shadowed
The suggester analyzes git history to recommend owners for unowned files.
How it works:
- Find unowned files - Files not matched by any CODEOWNERS rule
- Analyze git blame - For each file, get line-by-line author information
- Weight by recency - Recent changes matter more than ancient history
- Aggregate by author - Sum weighted contributions per author
- Resolve to teams - Use
lookup_cmdto map emails → team names - Match existing owners - Fuzzy-match against owners already in CODEOWNERS
- Calculate confidence - Based on contribution concentration and history depth
The lookup_cmd config:
lookup_cmd = "your-tool lookup {email} | jq -r .team"The command receives a git email and should output a team/owner identifier. This gets fuzzy-matched against existing CODEOWNERS entries to maintain consistency.
Confidence scoring:
- High (70%+): Single dominant contributor, maps cleanly to existing team
- Medium (40-70%): Clear top contributor but some ambiguity
- Low (<40%): Scattered ownership, multiple teams, or unmapped authors
Use --min-confidence to filter suggestions.
MIT