Plugin lspServers entries declaring restartOnCrash or shutdownTimeout are silently dropped by the LSP registrar (schema-valid, no diagnostic)
Summary
On Claude Code (isolated on 2.1.195, re-confirmed on current 2.1.201), a plugin's plugin.json lspServers server entry is silently dropped by
the runtime LSP registrar if it declares restartOnCrash or shutdownTimeout. Both keys are
accepted by the plugin-manifest JSON schema -- the manifest passes claude plugin validate -- but
the registrar rejects any entry carrying either one, with no diagnostic anywhere in the event
stream. The server never registers, and every LSP operation on the affected file type returnsNo LSP server available for file type: .... Removing both fields restores registration.
This is a schema-permits / registrar-rejects mismatch. It is distinct from the LspServerManager
init-ordering race (#14803, fixed) and from the marketplace packaging gap
(anthropics/claude-plugins-official#379): control plugins register on the same build, and the entry
here is read from plugin.json, which the installer does copy.
Environment
- Claude Code: isolated on 2.1.195 (2026-06-26) via a controlled single-field matrix;
re-confirmed on 2.1.201 (2026-07-06) via a two-plugin control differing only by the two fields.
- Plugin: powershell-lsp (standalone repo, https://github.com/manderse21/claude-powershell-lsp);
server declared in plugin.json lspServers.
- PSES v4.6.0, PSScriptAnalyzer 1.25.0; Windows 11.
- Harness: one builtin-
LSPgoToDefinition, driven through a fresh non-interactive
claude -p --allowedTools LSP --strict-mcp-config --mcp-config '{"mcpServers":{}}';
--output-format stream-json --verbosetool_use / tool_result read from the event stream
(not a prompt echo). The plugin was installed via claude plugin marketplace add <local-dir> +
claude plugin install (the installer populates the cache), then probed from a fresh
claude -p with no --plugin-dir.
Reproduction
Start from a known-good plugin.json lspServers entry that registers and serves -- a clean block
declaring command, args, extensionToLanguage, transport, startupTimeout, maxRestarts,env (each of these was confirmed registrar-clean when added alone). Then:
- Add
restartOnCrashto the entry.goToDefinitionon a mapped file returns
No LSP server available for file type: ... -- the extension-to-server mapping is not
registered.
- Revert, and add
shutdownTimeoutinstead. Same result -- not registered. - Remove both. The entry registers and Claude Code launches the server.
Both fields validate under the manifest schema; neither drop emits any registrar diagnostic.
Single-field isolation matrix (Claude Code 2.1.195)
Signal strings:
No LSP server available for file type: .ps1-> the mapping was NOT registered.No definition found/Defined in ...-> registered and served (a known-good command).... timed out after 30000ms during initialization-> registered (the server launched; init did
not complete -- a separate serve-layer matter, see "Scope" below).
| Variant | Block delta | .ps1 result |
|---------|-------------|---------------|
| Known-good block in plugin.json | clean block, no restart/shutdown fields | REGISTER (served) |
| Known-good + transport / maxRestarts / env | each added alone | REGISTER (served) |
| Known-good + restartOnCrash (alone) | breaker | FAIL (no server) |
| Known-good + shutdownTimeout (alone) | breaker | FAIL (no server) |
| Shipped block | both breakers present | FAIL (no server) |
| Shipped minus restartOnCrash | one breaker removed, shutdownTimeout remains | FAIL (no server) |
| Shipped minus both breakers | the fix | REGISTER (launch; init timeout) |
Both fields must be removed: removing only one still fails. An earlier suspect -- anenv / ${CLAUDE_PLUGIN_DATA} block -- was refuted (known-good + env registers).
Re-confirmation on 2.1.201
The drop reproduces on the current build. Two throwaway plugins were installed one at a time, each
mapping a unique test extension (.ps1x) and differing only in that one declared restartOnCrash +shutdownTimeout. A single documentSymbol call against the same file gave:
- clean entry: reached a launch attempt and returned the separate
#73961Windows launcher-guard
error (Command 'pwsh' not found or is in an unsafe location) -- i.e. it registered.
- breaker entry:
No LSP server available for file type: .ps1x-- it never registered.
On current Windows the clean entry surfaces the #73961 launcher guard at spawn rather than a served
result; that still evidences registration (see Scope). The 2.1.195 matrix above remains the primary
single-field isolation; this is an equivalent two-plugin re-confirmation on the current build.
Which manifest is read
When a plugin ships a plugin.json, Claude Code registers lspServers from plugin.json and
ignores the marketplace.json entry; the marketplace.json lspServers is consulted only for
plugins that ship no plugin.json (as the official LSP plugins do -- they ship only LICENSE +README and register from the marketplace entry). A plugin that needs a plugin.json -- for hooks
or userConfig -- must therefore carry a registrar-clean lspServers there; putting it only inmarketplace.json is inert while a plugin.json exists. This is why the two-field drop is
load-bearing rather than a packaging artifact: it is independent of anthropics/claude-plugins-official#379.
Suggested resolution
Either honor restartOnCrash / shutdownTimeout at registration, or -- if they are intentionally
unsupported at the registrar -- reject them at claude plugin validate with a named error, so the
schema-permits / registrar-rejects gap surfaces at authoring time instead of failing silently at
runtime. Failing that, a single event-stream warning when the registrar drops a schema-valid field
would localize this whole class of silent failure.
Relationship to existing issues
- #14803 (registration race, fixed) / #15168 / #15148: the init-ordering race is not this. Control
plugins (e.g. the official typescript LSP) register on the same build, so the registrar runs;
the miss here is the two-field drop, a distinct defect.
- anthropics/claude-plugins-official#379 (marketplace packaging gap): real and open, but
independent -- registration succeeds once the plugin.json block is registrar-clean.
- anthropics/claude-plugins-official#1359-class server-to-client init handshake: the remaining serve-layer gap after registration is
restored (the launched server's init times out because the client rejects standard
server-to-client requests). Separate from registration; not this report.
- #73961 (Windows native LSP launcher refusing bare commands): a separate, later, spawn-layer
blocker on Windows; also not this report.
Scope
This report is about registration only. It does not claim native serve works -- serve is separately
gated (anthropics/claude-plugins-official#1359), and on Windows the launcher separately refuses bare commands (#73961). The plugin's
own diagnostics path does not use native LSP and is unaffected in either state.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗