LSP plugins fail on Windows: spawn ENOENT for npm global binaries
Bug Description
LSP plugins (e.g., typescript-lsp@claude-plugins-official) fail to start on Windows because the process is spawned without shell: true, which prevents resolution of .cmd wrappers that npm uses for global binaries.
Environment
- OS: Windows 11 Pro
- Claude Code: Latest
- Plugin:
typescript-lsp@claude-plugins-officialv1.0.0 - Binary:
typescript-language-serverv5.1.3 (installed globally via npm)
Steps to Reproduce
- Install the TypeScript LSP plugin:
claude plugins install typescript-lsp@claude-plugins-official - Install the language server:
npm install -g typescript-language-server typescript - Verify the binary works from the shell:
typescript-language-server --version→5.1.3 - Use the LSP tool in Claude Code → fails
Error
Error performing documentSymbol: ENOENT: no such file or directory, uv_spawn 'typescript-language-server'
Subsequent attempts show:
Error performing documentSymbol: Failed to sync file open: Cannot send notification to LSP server 'plugin:typescript-lsp:typescript': server is error
Root Cause
On Windows, npm install -g creates three files for each binary:
typescript-language-server(POSIX shell script)typescript-language-server.cmd(Windows batch wrapper)typescript-language-server.ps1(PowerShell wrapper)
Claude Code's LSP plugin appears to spawn the process using child_process.spawn() without { shell: true }. Without a shell, Windows cannot resolve .cmd extensions or execute shell scripts, resulting in ENOENT.
Comparison
Other AI coding tools (e.g., OpenCode) successfully launch the same LSP servers on the same Windows machine, likely because they spawn with shell: true or via cmd.exe, which handles .cmd resolution automatically.
Expected Behavior
LSP servers installed via npm install -g should work on Windows.
Suggested Fix
Either:
- Pass
{ shell: true }tochild_process.spawn()when launching LSP servers on Windows - On Windows, automatically append
.cmdto the command name if the bare name isn't found - Resolve the binary path using
which/wherebefore spawning
This issue has 7 comments on GitHub. Read the full discussion on GitHub ↗