Plugin LSP server silently not registered when its lspServers entry declares restartOnCrash or shutdownTimeout (schema-valid fields the runtime registrar drops)

Open 💬 0 comments Opened Jun 27, 2026 by manderse21

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 env block with a ${CLAUDE_PLUGIN_DATA} substitution -- registers (fine)
  • shutdownTimeout: <n> -- DROPS registration
  • restartOnCrash: 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.)

View original on GitHub ↗