Plugin LSP server silently not registered when its lspServers entry declares restartOnCrash or shutdownTimeout (schema-valid fields the runtime registrar drops)
TITLE: Plugin LSP server silently not registered when its lspServers entry declares restartOnCrash or shutdownTimeout (schema-valid fields the runtime registrar drops)
BODY:
Summary
On Claude Code 2.1.195, a plugin-provided LSP server is silently not registered for its file types if its lspServers entry declares restartOnCrash or shutdownTimeout. Both fields are accepted by the plugin-manifest JSON schema, and claude plugin details still lists the server as a component -- but the file-type lookup returns "No LSP server available for file type: ...". There is no diagnostic in the debug/stream-json output explaining the drop. Removing either field flips the same plugin from unregistered to registered. This is a schema-permits / registrar-rejects mismatch.
This is distinct from the earlier "0 servers registered" init-ordering issue, which appears resolved at 2.1.76 (an official plugin registers and serves fine on 2.1.195 -- see Control below).
Environment
- Claude Code 2.1.195 (Windows; pwsh 7.6.3 and node-based CLI).
- Plugin installed at user scope from a marketplace (loads from the plugin cache, not via
--plugin-dir).
Control (confirms the platform path works)
typescript-language-server --stdio declared in a plugin's LSP entry, mapped to a unique extension, registers and serves goToDefinition on 2.1.195. So plugin LSP registration through the cache path works; the issue below is specific to two fields.
Minimal reproduction
Start from a plugin whose LSP server registers (known-good). For example, a plugin plugin.json containing:
{
"name": "repro",
"version": "1.0.0",
"lspServers": {
"repro": {
"command": "typescript-language-server",
"args": ["--stdio"],
"extensionToLanguage": { ".ts": "typescript" }
}
}
}
Install it (any extension/server that registers will do); confirm goToDefinition on a matching file returns a real result (registration works).
Now add a single field to that same entry:
"restartOnCrash": true
Reinstall / reload and probe again. Result: "No LSP server available for file type: .ts". The server is no longer registered -- even though claude plugin details still lists "LSP servers (1) repro".
The same happens with shutdownTimeout instead:
"shutdownTimeout": 5000
Each field independently suppresses registration. Removing both restores it.
What I observed across a controlled bisection
Holding everything else constant and toggling one field at a time (known-good command, so launch never enters the picture):
transport: "stdio"-- registers (fine)startupTimeout: <n>-- registers (fine; also used officially, e.g. jdtls)maxRestarts: <n>-- registers (fine)- an
envblock with a${CLAUDE_PLUGIN_DATA}substitution -- registers (fine) shutdownTimeout: <n>-- DROPS registrationrestartOnCrash: true-- DROPS registration
Because "No LSP server available for file type" is a registration-time miss (it precedes any server launch), and plugin details confirms the manifest was parsed, the entry is being discarded specifically during the extension-to-server registration step when either field is present.
Note on precedence (may be related)
When a plugin ships a plugin.json, lspServers is read from plugin.json and the marketplace.json entry is ignored; the marketplace.json lspServers is consulted only for plugins that ship no plugin.json (as the official LSP plugins do). A plugin that needs a plugin.json (for hooks/userConfig) therefore must declare a registrar-clean lspServers there. Flagging this in case the field-handling differs between the two code paths.
Ask
Either the manifest schema should reject restartOnCrash / shutdownTimeout for LSP entries, or the runtime registrar should honor them -- but it should not accept them at validation and then silently drop the whole server entry with no diagnostic. At minimum, a logged warning when an LSP entry is dropped during registration would have turned this into a one-minute diagnosis instead of a long bisection.
(Secondary, separate issue, mentioned only for completeness: once registration is restored, a server that launches a real language server -- in my case PowerShell Editor Services over stdio, which starts cleanly and emits well-framed LSP output -- still times out at the client during initialization. That looks like the client not answering the server's server-to-client requests during init, a different failure mode from this registration bug, and I will file it separately if it is not already tracked.)