LSP plugins fail on Windows: spawn() cannot find npm-installed .cmd binaries
Summary
LSP plugins (pyright-lsp, typescript-lsp, and any npm-installed language server) fail to start on Windows because child_process.spawn() without shell: true cannot execute .cmd wrapper scripts.
Root Cause
When npm installs a global package on Windows, it creates .cmd batch wrappers (e.g., pyright-langserver.cmd) in %APPDATA%\npm\. These are not .exe files.
spawn('pyright-langserver', ['--stdio']) calls CreateProcessW under the hood, which only resolves .exe and .com extensions. The .cmd wrapper is invisible to it, so the process fails with ENOENT.
Reproduction
- Install pyright-lsp or typescript-lsp plugin on Windows
- Open a
.pyor.tsfile - LSP features (diagnostics, go-to-definition, etc.) do not work
- In debug logs, spawn fails with ENOENT for
pyright-langserver
Environment
- Windows 11
- Node.js v22.x (installed via winget, not nvm)
- npm globals in
%APPDATA%\npm\ - Claude Code latest
Suggested Fix
Any of these would work:
- Use
shell: truein thespawn()options when launching LSP servers on Windows. This letscmd.exeresolve.cmdwrappers naturally.
- Platform-aware command resolution: On Windows, check if
command + '.cmd'exists in PATH before spawning, and if so, either useshell: trueor resolve the.cmdto its underlyingnode+ JS entry point.
- Allow local LSP config overrides: Let users override
lspServersentries in a local config file that doesn't get overwritten by plugin updates. Currently,marketplace.jsonis the only config source and gets overwritten on every plugin update.
Current Workaround
Manually patch marketplace.json to use node as the command with the JS entry point as the first argument:
// Before (fails on Windows):
"command": "pyright-langserver",
"args": ["--stdio"]
// After (works):
"command": "node",
"args": ["C:/Users/<user>/AppData/Roaming/npm/node_modules/pyright/langserver.index.js", "--stdio"]
This must be re-applied after every plugin update since marketplace.json is overwritten.
Related
The csharp-ls plugin (via dotnet tool install) has a separate issue: Claude Code's LSP client is missing handlers for workspace/configuration, client/registerCapability, and window/workDoneProgress/create requests that csharp-ls requires during initialization. This causes the server to hang waiting for responses that never come.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗