[FEATURE] LSP: path/glob-based routing (filePatterns) so servers can claim some .yml files without claiming all of them

Status Open
Maintainer reply None cached
Activity 0 comments · opened Sep 29, 2026

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:

  1. False diagnostics. The server is sent didOpen for 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.
  2. 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 filePatterns is set, the server is a candidate for a file only if the extension matches extensionToLanguage and the path matches one of the patterns. extensionToLanguage still supplies the languageId, and existing plugins are unaffected.
  • More specific wins. When several servers claim an extension, prefer the ones whose filePatterns match over those without patterns, instead of "first registered wins". A CircleCI plugin and a general YAML plugin could then coexist: .circleci/config.yml goes to the CircleCI server and every other .yml goes 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 if matchers (**, {a,b}), so plugin authors have only one syntax to learn.
  • Validation. Have claude plugin validate check the field. #92304 notes that .lsp.json isn'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|Write with a path check, which runs a CLI validator and returns the findings as additionalContext. It targets the right files, but it only restores diagnostics after edits. The model loses hover, go-to-definition, references and symbols through the LSP tool.
  • 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

  1. A user enables a CircleCI plugin that bundles the CircleCI YAML language server, plus a GitHub Actions or general YAML LSP plugin.
  2. Claude edits .circleci/config.yml. The CircleCI server gets didOpen and publishes diagnostics about an undefined job, an unknown orb command and so on, and the model fixes them in the same turn.
  3. Claude edits docker-compose.yml or .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. |

View original on GitHub ↗