kotlin-lsp plugin: LSP servers lost during reinitializeLspServerManager (generation 2 overwrites with 0 servers)
Problem
The official kotlin-lsp plugin from claude-plugins-official marketplace fails to start the LSP server process. The LSP manager initializes correctly on first pass (generation 1) but a subsequent reinitializeLspServerManager() call (generation 2) overwrites the server list with 0 servers.
Debug Log Evidence
Generation 1 (correct):
[LSP MANAGER] Starting async initialization (generation 1)
Loaded 1 LSP server(s) from plugin: kotlin-lsp
Total LSP servers loaded: 2
[LSP SERVER MANAGER] getAllLspServers returned 2 server(s)
LSP manager initialized with 2 servers
Generation 2 (overwrites):
[LSP MANAGER] reinitializeLspServerManager() called
[LSP MANAGER] Starting async initialization (generation 2)
Loaded plugins - Enabled: 4, Disabled: 0, Commands: 2, Agents: 6, Errors: 0
Total LSP servers loaded: 0
[LSP SERVER MANAGER] getAllLspServers returned 0 server(s)
LSP manager initialized with 0 servers
Root Cause Analysis
The lspServers configuration for kotlin-lsp is defined in marketplace.json, not in the cached plugin.json. During generation 1, the LSP manager reads from marketplace.json and correctly finds the LSP server config. However, when reinitializeLspServerManager() is triggered (by file/settings watch changes ~200ms later), it appears to only read from the cached plugin.json, which contains no lspServers field:
Cached plugin.json (~/.claude/plugins/cache/claude-plugins-official/kotlin-lsp/1.0.0/.claude-plugin/plugin.json):
{
"name": "kotlin-lsp",
"description": "Kotlin language server for code intelligence",
"version": "1.0.0",
"author": {
"name": "Anthropic",
"email": "support@anthropic.com"
}
}
marketplace.json (has the config):
{
"name": "kotlin-lsp",
"source": "./plugins/kotlin-lsp",
"lspServers": {
"kotlin-lsp": {
"command": "kotlin-lsp",
"args": ["--stdio"],
"extensionToLanguage": {
".kt": "kotlin",
".kts": "kotlin"
}
}
}
}
The plugin source directory also has no .lsp.json file — only README.md and LICENSE.
Workaround
Created a local marketplace with .lsp.json in the plugin directory, so the LSP config is read from the plugin itself rather than relying on marketplace.json:
kotlin-lsp-local/
├── .claude-plugin/
│ └── marketplace.json
└── plugins/
└── kotlin-lsp/
├── .claude-plugin/
│ └── plugin.json
└── .lsp.json ← LSP config here
This survives reinitialize because the config is in the plugin's own .lsp.json.
Environment
- Claude Code: 2.1.92
- OS: macOS 15.7.4 (ARM64)
- kotlin-lsp binary: installed via
brew install JetBrains/utils/kotlin-lsp - kotlin-lsp binary works correctly when tested directly with LSP protocol framing
Expected Behavior
The reinitializeLspServerManager() should preserve LSP server configurations from marketplace.json, or the official plugin should include a .lsp.json file so the config is available from the plugin directory itself.
Additional Note
The official docs page (https://code.claude.com/docs/en/discover-plugins#code-intelligence) lists the required binary as kotlin-language-server, while the marketplace.json uses kotlin-lsp as the command. The README says brew install JetBrains/utils/kotlin-lsp. These should be consistent.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗