LSP plugins fail to spawn npm-installed language servers on Windows (.cmd wrappers not resolved by uv_spawn)

Resolved 💬 3 comments Opened Apr 29, 2026 by kyk234 Closed May 3, 2026

Summary

On Windows, LSP plugins (e.g., typescript-lsp) fail to spawn language-server binaries that npm installs as .cmd shims. The LSP tool returns:

Error performing <op>: ENOENT: no such file or directory, uv_spawn 'typescript-language-server'

even when where typescript-language-server resolves to a valid .cmd and the binary runs fine from a normal shell.

Repro

  1. Windows 10/11. Native Node (e.g. v24.11.1) installed at C:\Program Files\nodejs.
  2. npm install -g typescript-language-server typescript
  3. Enable plugin typescript-lsp@claude-plugins-official in ~/.claude/settings.json.
  4. Call any LSP operation (documentSymbol, hover, workspaceSymbol, ...) on a .ts file.

Expected: LSP returns symbol info.
Actual: uv_spawn 'typescript-language-server' fails with ENOENT.

Root cause

On Windows, npm global installs only create .cmd / .ps1 / extension-less shims for binaries (no real .exe). libuv's uv_spawn, when called without a shell, does not search PATHEXT for .cmd / .bat — it only resolves .exe. Same caveat applies to Node's child_process.spawn with shell: false. Linux/macOS work because npm writes a real executable shim.

Workaround

Built a tiny PE shim via Add-Type -OutputType ConsoleApplication:

using System;
using System.Diagnostics;
class L {
    static int Main(string[] a) {
        var psi = new ProcessStartInfo {
            FileName = "node.exe",
            UseShellExecute = false
        };
        psi.Arguments = "\"<path-to-cli.mjs>\"";
        foreach (var x in a) psi.Arguments += " \"" + x.Replace("\"","\\\"") + "\"";
        var p = Process.Start(psi);
        p.WaitForExit();
        return p.ExitCode;
    }
}

Dropping the resulting typescript-language-server.exe next to the .cmd in %APPDATA%\npm\ resolves it (PATHEXT prefers .EXE over .CMD).

Suggested fix (any of)

  • Spawn LSP servers with shell: true on Windows.
  • Resolve via where.exe <name> first and call the absolute path.
  • Allow plugin authors to override the spawn command via plugin manifest (e.g. a command / args field in the plugin descriptor).

Affected plugins

Likely all *-lsp plugins on Windows that rely on npm-installed launchers (typescript-lsp, possibly others depending on how their language server distributes Windows entrypoints).

Environment

  • OS: Windows 10 Enterprise 19045
  • Node: v24.11.1 (native install at C:\Program Files\nodejs)
  • typescript-language-server: 5.1.3
  • Claude Code: latest as of 2026-04-29

View original on GitHub ↗

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