Kuberwastaken/claurst · critical · anyhow::Error
Failed to start LSP server
Error message
Failed to start LSP server '{}': {} What it means
`LspClient::start` failed to spawn the configured language-server executable. `tokio::process::Command::spawn` returned an OS error (typically ENOENT, permission denied, or exec-format error), and the library wraps it with the configured command name for context. This happens before any LSP protocol exchange, so no server process exists.
Solutions
- Verify the server binary exists and is runnable: `which <command>` / `command -v <command>`, then run it directly with `--version`.
- Install the language server (rustup component add rust-analyzer, npm i -g pyright, etc.) or set `config.command` to its absolute path.
- If the binary lives outside the default PATH of the invoking environment, add its directory to PATH or use an absolute path in LspServerConfig.
- Check config.env entries aren't clobbering PATH or other vars the launcher needs.
- On Windows, invoke .cmd/.bat wrappers via cmd /c or point directly at the .exe.
Example fix
// before
let cfg = LspServerConfig { name: "rust".into(), command: "rust-anaylzer".into(), args: vec![], env: Default::default() };
// after
let cfg = LspServerConfig { name: "rust".into(), command: "/usr/bin/rust-analyzer".into(), args: vec![], env: Default::default() }; Defensive patterns
Strategy: validation
Validate before calling
fn assert_server_available(command: &str, args: &[String]) -> std::io::Result<()> {
let mut cmd = std::process::Command::new(command);
cmd.args(args).arg("--version").stdout(std::process::Stdio::null()).stderr(std::process::Stdio::null());
cmd.status().map(|_| ())
} Try / catch
match LspClient::start(config).await {
Ok(client) => client,
Err(e) if e.to_string().contains("Failed to start LSP server") => {
eprintln!("server '{}' missing or not executable: {e}", config.command);
return Err(e);
}
Err(e) => return Err(e),
} Prevention
- Resolve the server binary to an absolute path in config instead of relying on PATH.
- Run `which <server>` / `<server> --version` as a startup health check.
- Pin the server install in setup scripts (rustup component add, npm i -g) so environments are consistent.
- Beware CI/container PATH differences; export needed dirs explicitly.
When it happens
Trigger: Calling `LspClient::start(config)` where `config.command` is not on PATH, points to a nonexistent file, lacks the executable bit, or fails to exec (e.g. wrong interpreter shebang, wrong-architecture binary, or a .cmd/.bat path on Windows without a shell).
Common situations: Typo'd command like 'rust-anaylzer'; server not installed (no `rust-analyzer`, `gopls`, `pyright` in PATH); running under systemd/CI with a minimal PATH missing ~/.cargo/bin or npm global bin; executable bit stripped after download; server requires an interpreter (node/python) that isn't installed.
Related errors
- LSP server stdin not available
- LSP server stdout not available
- LSP client already shut down
- LSP request ' ' timed out (server: )
- LSP request ' ' channel closed (server: )
AI-assisted analysis of Kuberwastaken/claurst@b0637c97ec (2026-09-10).
Data as JSON: /api/errors/e7b659cdf1d63677.
Report an issue: GitHub.
Appendix: source
Thrown at src-rust/crates/core/src/lsp.rs:209
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.kill_on_drop(true);
// Inject environment variables
for (k, v) in &config.env {
cmd.env(k, v);
}
// On Windows, suppress the console window (CREATE_NO_WINDOW = 0x0800_0000).
// tokio::process::Command exposes creation_flags() directly on Windows.
#[cfg(target_os = "windows")]
{
cmd.creation_flags(0x0800_0000u32);
}
let mut child = cmd.spawn().map_err(|e| {
anyhow::anyhow!(
"Failed to start LSP server '{}': {}",
config.command,
e
)
})?;
let stdin = child
.stdin
.take()
.ok_or_else(|| anyhow::anyhow!("LSP server stdin not available"))?;
let stdout = child
.stdout
.take()
.ok_or_else(|| anyhow::anyhow!("LSP server stdout not available"))?;
let pending: PendingMap = Arc::new(DashMap::new());
let diagnostics: Arc<DashMap<String, Vec<LspDiagnostic>>> =
Arc::new(DashMap::new());
View on GitHub (pinned to b0637c97ec)