[FEATURE] LSP: path/glob-based routing (filePatterns) so servers can claim some .yml files without claiming all of them
Preflight Checklist
- [x] I have searched existing requests. The closest is #92304 (filename-only routing for extensionless files). This asks for something related but different: routing on the file's path, so that a server can claim some files of a common extension without claiming all of them.
- [x] This is a single feature request
Problem Statement
Plugin LSP servers are matched to files by path.extname() alone, through extensionToLanguage. Many tools ship a language server for a config file whose extension it shares with every other file of that format:
- CircleCI:
.circleci/config.yml - GitHub Actions:
.github/workflows/*.yml - GitLab CI:
.gitlab-ci.yml - Docker Compose, Kubernetes manifests, Helm charts, OpenAPI specs and so on.
Such a server can only be registered for every .yml/.yaml file in the workspace. That causes two problems:
- False diagnostics. The server is sent
didOpenfor every YAML file the agent touches, and it has to recognise and ignore the ones it doesn't own. If it gets that wrong, the model receives irrelevant diagnostics after each edit. - Plugins shadow each other. If two enabled plugins both claim
.yml, the first one registered wins and the other never starts (see #32912). Two unrelated plugins, for example a CircleCI one and a GitHub Actions one, or either of them and a general YAML server, can't be enabled together, even though their files never overlap. The user gets no warning.
Editors solved this long ago with document selectors:
- VS Code:
DocumentFilter.pattern, e.g.**/.circleci/**/*.{yml,yaml} - IntelliJ:
isSupportedFile - Kiro:
file_patterns - Neovim/Zed: root markers and file-type globs
In our VS Code extension, the CircleCI YAML language server (https://github.com/CircleCI-Public/circleci-yaml-language-server) is scoped to **/.circleci/**/*. We'd like to ship the same server as a Claude Code plugin, but in Claude Code the plugin would have to claim all YAML.
Proposed Solution
Add an optional, per-server list of glob patterns, matched against the file's path relative to the workspace root (and against the absolute path, for files outside it):
{
"circleci-yaml": {
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/circleci-yaml-lsp",
"args": ["-stdio"],
"extensionToLanguage": { ".yml": "yaml", ".yaml": "yaml" },
"filePatterns": ["**/.circleci/**/*.{yml,yaml}"]
}
}
Suggested semantics:
- Narrowing, not widening. When
filePatternsis set, the server is a candidate for a file only if the extension matchesextensionToLanguageand the path matches one of the patterns.extensionToLanguagestill supplies thelanguageId, and existing plugins are unaffected. - More specific wins. When several servers claim an extension, prefer the ones whose
filePatternsmatch over those without patterns, instead of "first registered wins". A CircleCI plugin and a general YAML plugin could then coexist:.circleci/config.ymlgoes to the CircleCI server and every other.ymlgoes to the general one. This doesn't need concurrent servers per file (#32912). - Glob dialect. Use the one Claude Code already has for permission rules and hook
ifmatchers (**,{a,b}), so plugin authors have only one syntax to learn. - Validation. Have
claude plugin validatecheck the field. #92304 notes that.lsp.jsonisn't validated today.
#92304's filenameToLanguage could fit the same mechanism: a pattern such as **/justfile covers the basename case too, so a single field might serve both requests.
Alternative Solutions
- Server-side gating (what we'll ship in the meantime). The server returns empty diagnostics and null hover/definition for paths outside
.circleci/. That fixes the false diagnostics, but every YAML file still starts and syncs with our server, and the plugin still shadows or is shadowed by any other YAML plugin. - A PostToolUse hook matched on
Edit|Writewith a path check, which runs a CLI validator and returns the findings asadditionalContext. It targets the right files, but it only restores diagnostics after edits. The model loses hover, go-to-definition, references and symbols through theLSPtool. - An MCP bridge around the language server. Diagnostics become pull-only, and the model has to remember to ask for them.
Priority
Medium - Would be very helpful
Feature Category
Developer tools/SDK
Use Case Example
- A user enables a CircleCI plugin that bundles the CircleCI YAML language server, plus a GitHub Actions or general YAML LSP plugin.
- Claude edits
.circleci/config.yml. The CircleCI server getsdidOpenand publishes diagnostics about an undefined job, an unknown orb command and so on, and the model fixes them in the same turn. - Claude edits
docker-compose.ymlor.github/workflows/ci.yml. The CircleCI server is never involved, and the other plugin's server handles the file.
Today, step 2 or step 3 breaks, depending on which plugin registered first.
Additional Context
Related issues
| Issue | State | Relation |
| --- | --- | --- |
| #92304 | open | filenameToLanguage, which matches on basename. That's the same routing map, but a basename can't express "only under .circleci/". |
| #47748 | closed, stale | Earlier version of #92304. |
| #32912 | closed, not planned | Multiple servers per extension. Path patterns would remove most of the need for it, because tool-specific servers would stop fighting over .yml. |
| #15785 | closed, not planned | Compound extensions (docker-compose.yml). Same root cause. |