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
# 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
- Go-to-Definition: Jump to the CODEOWNERS rule that matches the current 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)
- Linked Editing: Edit an owner and all occurrences update simultaneously
- 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 | ✅ |
| 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) | ✅ |
| Pattern hover (show matches) | ✅ |
| Related diagnostics (shadowed links) | ✅ |
| CLI: suggest (git-based suggestions) | |
| CLI: optimize (pattern consolidation) | ✅ |
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