[FEATURE] LSP: filename-based routing (filenameToLanguage) for extensionless canonical files such as justfile, Makefile, Dockerfile
Preflight Checklist
- [x] I have searched existing requests and this feature hasn't been requested yet — it was requested as #47748, which was closed by the stale bot without a maintainer response and is locked; see Related issues below.
- [x] This is a single feature request (not multiple features)
Related issues
| Issue | State | Relation |
| --- | --- | --- |
| #47748 | closed not planned by stale bot 2026-06-08, locked | same request (filenameToLanguage, Docker/Make/Gemfile use case); this issue re-files it with runtime and binary evidence, as its closing comment asks |
| #15785 | closed not planned 2026-02-14 | compound extensions (docker-compose.yml): same root cause, path.extname is the only key |
| #32912 | closed not planned 2026-05-30 | multiple servers per extension: same routing map, adjacent limitation |
| #89472 | open | project-scoped LSP config; states a repo-root .lsp.json is not discovered, so the only place to add filename routing today is a plugin |
Problem Statement
Plugin LSP servers are matched to files by file extension only. Languages whose canonical entrypoint is a file name with no extension cannot be routed to a language server by any plugin configuration.
Verified on Claude Code 2.1.261 (Windows 11, binary sha256 f2f5d1a155167488aeb32cd263e15436253c7b1681ae147c9e73e4d6bbc3c852):
Server selection:
function F(xe){let De=FN.extname(xe).toLowerCase(),Ne=o.get(De);if(!Ne||Ne.length===0)return;...}
extensionToLanguage key schema:
mc=m(()=>s().min(2).refine((e)=>e.startsWith("."),{message:'File extensions must start with dot (e.g., ".ts", not "ts")'}))
path.extname (Node v25.9.0) for canonical names: justfile, Justfile, .justfile, Makefile, GNUmakefile, Dockerfile, BUILD, WORKSPACE, Gemfile, Rakefile, Vagrantfile, Tiltfile → "". The only map key that could match is "", and the schema rejects it. A plugin declaring "extensionToLanguage": {"": "just"} fails at session start:
LSP config validation failed for .lsp.json in plugin just-lsp-emptykey-probe: [ { "code": "invalid_key", ... "minimum": 2 ... "File extensions must start with dot" ... } ]
The per-server schema is closed. Fields: command, args, extensionToLanguage, transport, env, initializationOptions, settings, workspaceFolder, startupTimeout, shutdownTimeout, restartOnCrash, maxRestarts, diagnostics. No filename or glob field exists.
Reproduction with terror/just-lsp 0.7.1:
p/.claude-plugin/plugin.json
{ "name": "just-lsp", "lspServers": "./.lsp.json" }
p/.lsp.json
{ "just": { "command": "just-lsp", "extensionToLanguage": { ".just": "just" } } }
Identical fixture content as ext/foo.just and lower/justfile.
claude -p --plugin-dir ./p --tools LSP --debug-file ext.log \
"Call the LSP tool exactly once with operation=documentSymbol, filePath=$PWD/ext/foo.just, line=1, character=1. Reply with the raw result."
claude -p --plugin-dir ./p --tools LSP --debug-file lower.log \
"Call the LSP tool exactly once with operation=documentSymbol, filePath=$PWD/lower/justfile, line=1, character=1. Reply with the raw result."
| File | Tool result | Debug log |
| --- | --- | --- |
| foo.just | 9 document symbols | LSP: Sent didOpen for .../foo.just (languageId: just), Received diagnostics from plugin:just-lsp:just: 3 diagnostic(s) |
| justfile | No LSP server available for file type: | No LSP server available for file type for operation documentSymbol on file .../lower/justfile |
| Justfile, .justfile | same | same |
The 2.1.261 binary already contains a filename map for syntax highlighting: new Map([["Dockerfile","dockerfile"],["Makefile","makefile"],["Rakefile","ruby"],["Gemfile","ruby"],["CMakeLists","cmake"]]). The LSP router does not consult it.
Proposed Solution
Add an optional filename map to the per-server LSP config, consulted in server selection and in the languageId lookup for textDocument/didOpen, matched against path.basename(file):
{
"just": {
"command": "just-lsp",
"extensionToLanguage": { ".just": "just" },
"filenameToLanguage": { "justfile": "just", ".justfile": "just" }
}
}
Matching: exact basename, case-insensitive (just itself resolves the justfile name case-insensitively). Optional glob form for names such as Dockerfile.*. Selection order: extensionToLanguage on path.extname, then filenameToLanguage on path.basename. The didOpen path already falls back to "plaintext" for an unmapped extension, so the change adds one key space to the selection map and the languageId lookup. Existing plugins are unaffected.
Alternative Solutions
- A PostToolUse hook with
ifrules such asEdit(//**/[Jj][Uu][Ss][Tt][Ff][Ii][Ll][Ee])that runs the server's CLI diagnostic mode (just-lsp analyze <file>) and returns the report ashookSpecificOutput.additionalContext. Restores diagnostics after edits only; hover, definition, references, and symbols through the LSP tool remain unavailable for the name. Shipped in primeinc/claude-just-lsp. - A symlink
justfile.just -> justfileroutes the LSP tool; Claude Code refuses to write through symlinks, so it is read-only. A hardlink splits on the first Edit because writes are temp-file plus rename. - Third-party binary patch:
camjac251/cc-enhancedpatcheslsp-filename-schemaandlsp-multi-serveraddfilenames/filenamePatternsand a basename fallback ingetServerForFile.
Priority
Medium - Would be very helpful
Feature Category
Configuration and settings
Use Case Example
- A repository's build entrypoint is a single file named
justfile(orMakefile,Dockerfile,Gemfile). - The language server for that language is installed and a plugin declares it in
.lsp.json. - Claude edits the file. No
didOpenis sent, no diagnostics are published, the LSP tool returnsNo LSP server available for file type:. - With
filenameToLanguage, the server attaches to the file by name and Claude receives diagnostics, hover, and definitions as it does for*.justtoday.
Additional Context
claude plugin validate, with and without--strict, does not read.lsp.json; a plugin whose.lsp.jsonthe runtime rejects passes validation. Surfacing.lsp.jsonschema errors inplugin validatewould have shortened this investigation.- Full write-up with binary offsets, logs, and the shadow-file experiments: https://github.com/primeinc/claude-just-lsp/blob/main/docs/claude-code-extensionless-routing.md
- Corroborating schema extraction from 2.1.261:
retif/claudecode-lintercontracts/lsp.schema.json(13 fields,additionalProperties: false).