[BUG] Windows: plugin lspServers bare command (e.g. pwsh, pyright-langserver) refused pre-spawn with 'not found or is in an unsafe location (current directory)' -- breaks official pyright-lsp plugin
Summary. On Windows, Claude Code's native LSP launcher refuses to start a plugin's LSP server
when the server's lspServers.command is a bare name. It fails at spawn withCommand '<cmd>' not found or is in an unsafe location (current directory); no server process
starts. This reproduces for the official pyright-lsp plugin (command: "pyright-langserver")
and for any plugin using command: "pwsh" (e.g. a PowerShell LSP plugin). It looks like the same
where.exe-based command resolver reported in #67821 (bare cmd in /desktop) and #42135 (baregit in marketplace update), now applied to the plugin LSP launcher.
Environment. Claude Code 2.1.200, Windows 11. Reproduced across fresh sessions. where.exe ->
pwshC:\Program Files\PowerShell\7\pwsh.exe; where.exe pyright-langserver andtypescript-language-server both resolve to npm shims on PATH.
Regression window (LSP launcher): (2.1.195, 2.1.200]. On 2.1.195 the same registered plugin's
launcher spawned bare pwsh (the server reached "Starting Language Server"; init then timed out).
On 2.1.200 it refuses pre-spawn. The 2.1.196-2.1.200 changelog documents no command-resolution or
LSP-launcher change.
Repro. Install pyright-lsp@claude-plugins-official; run the builtin LSP goToDefinition
on any .py file. Result: Command 'pyright-langserver' not found or is in an unsafe location. Same for a plugin whose
(current directory)lspServers.command is "pwsh".
The key inconsistency. In the SAME Claude Code process, a plugin's command-type HOOKS run the
bare pwsh ... command string successfully (they spawn via a shell), while the LSP launcher
refuses bare pwsh (it routes through the guarded programmatic spawn). So "the hooks work" does
not imply the LSP launcher will.
Mechanism (bundled resolver). The LSP spawn path routes command through a where.exe-based
safe-resolver that returns null -> the throw unless a candidate exists, sits outside the current
directory, and ends in .com/.exe/.bat/.cmd. Any command containing a path separator bypasses
the resolver entirely. On this machine where.exe pwsh / where.exe powershell DO resolve to
real System32 / Program Files executables, yet the launcher still refuses them -- matching #67821:
the resolver runs with a reduced/sanitized PATH and caches the failed resolution as null for the
session.
Variant table (throwaway scratch plugin; real builtin LSP tool via claude -p --plugin-dir):
| command | spawned? | result |
|---|---|---|
| pwsh (bare) | no | refused: "not found or is in an unsafe location (current directory)" |
| powershell (bare) | no | refused (identical) |
| C:\PROGRA~1\POWERS~1\7\pwsh.exe (absolute, space-free) | yes | spawned |
| ${CLAUDE_PLUGIN_ROOT}/serve.cmd (plugin-root wrapper) | yes | spawned |
Manifest-layer aggravator. The plugin-manifest validator rejects an lspServers.command
containing a space unless it starts with "/" ("Command should not contain spaces. Use args array
for arguments."), so the obvious workaround C:\Program Files\PowerShell\7\pwsh.exe failsclaude plugin validate / install. Only a space-free absolute path (8.3 short name) validates --
machine-specific and 8dot3-dependent.
Impact. Plugin-provided LSP navigation is unusable on Windows for any bare-command server,
including the official pyright-lsp plugin.
Suggested fixes. (a) Resolve LSP command the way the CLI already resolves where.exe
itself -- via an absolute system path / a non-sanitized PATH -- rather than through the
stale-PATH resolver; (b) do not cache negative resolutions permanently (per #67821); (c) support
a per-platform lspServers.command (or command + windows/unix overrides) so cross-platform
plugins can point at a platform-appropriate launcher; (d) surface the real resolution error
instead of the generic message. Same underlying resolver as #67821 / #42135.