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

  1. Verify the server binary exists and is runnable: `which <command>` / `command -v <command>`, then run it directly with `--version`.
  2. Install the language server (rustup component add rust-analyzer, npm i -g pyright, etc.) or set `config.command` to its absolute path.
  3. If the binary lives outside the default PATH of the invoking environment, add its directory to PATH or use an absolute path in LspServerConfig.
  4. Check config.env entries aren't clobbering PATH or other vars the launcher needs.
  5. 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

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


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)