TypeScript LSP plugin fails on Windows - spawn() doesn't handle npm wrapper scripts

Resolved 💬 3 comments Opened Jan 9, 2026 by awieckertNCS Closed Jan 9, 2026

Bug Description

The typescript-lsp plugin (and likely pyright-lsp and other npm-based LSP servers) fails to start on Windows with an ENOENT error.

Root Cause

When spawning LSP servers, Claude Code uses Node.js spawn() without the shell: true option. On Windows, this causes failures for npm-installed global packages because:

  1. npm creates wrapper scripts, not executables: When you run npm install -g typescript-language-server, npm creates text-based wrapper scripts (.cmd, .ps1, shell scripts) in the global bin directory - not actual .exe files.
  1. Windows kernel limitations: Unlike Unix (which understands shebangs), the Windows kernel can only directly execute .exe files. When spawn() asks Windows to run typescript-language-server, Windows finds the shell script (no extension) and fails because it's not a valid Win32 executable.
  1. Shells handle this transparently: When you type the command in CMD/PowerShell, the shell automatically finds and executes the .cmd wrapper. But spawn() bypasses this.

Why .NET LSP tools work

Tools installed via dotnet tool install -g create actual .exe executables, which Windows can run directly. This is why csharp-ls works but typescript-language-server doesn't.

Reproduction Steps

  1. Install Claude Code on Windows
  2. Run npm install -g typescript-language-server typescript
  3. Enable the typescript-lsp plugin
  4. Open a TypeScript project
  5. Observe LSP fails to initialize

Expected Behavior

LSP server should start successfully, as it does on macOS/Linux.

Suggested Fix

Option A: Use shell: true when spawning LSP servers on Windows:

spawn(command, args, { shell: process.platform === 'win32' });

Option B: Detect Windows and automatically wrap npm commands:

if (process.platform === 'win32' && !command.endsWith('.exe')) {
  spawn('cmd.exe', ['/c', command, ...args]);
}

Workaround

Users can manually edit marketplace.json to change:

{
  "command": "typescript-language-server",
  "args": ["--stdio"]
}

To:

{
  "command": "cmd.exe",
  "args": ["/c", "typescript-language-server", "--stdio"]
}

However, this fix is overwritten on plugin updates.

Environment

  • OS: Windows 10/11
  • Claude Code: Latest
  • Node.js: v20+
  • npm: v10+

Additional Context

This affects any LSP server installed via npm globally, not just TypeScript. The same issue likely affects:

  • pyright-lsp (pyright)
  • vscode-langservers-extracted (HTML, CSS, JSON LSPs)
  • bash-language-server
  • Any other npm-based language server

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗