Kuberwastaken/claurst · error · anyhow::Error

LSP server stdout not available

Error message

LSP server stdout not available

What it means

Same as the stdin case but for stdout: `child.stdout.take()` returned `None` in `LspClient::start`, so the client cannot build its response-reader pump. Because `start` always pipes stdout, this is effectively an internal invariant violation or an OS-level pipe-creation failure.

Solutions

  1. Retry startup; check and raise the open-file limit (`ulimit -n`) if fd exhaustion is suspected.
  2. Audit sandbox/container policies that may block pipe creation.
  3. Ensure no code path takes stdout from the Child before LspClient::start does.
  4. Report upstream if reproducible under normal conditions.
Defensive patterns

Strategy: retry

Try / catch

match LspClient::start(config.clone()).await {
    Ok(c) => c,
    Err(e) if e.to_string().contains("stdout not available") => retry_with_backoff(config, 3).await?,
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: `LspClient::start` with stdout pipes failing to be created by the OS, or the Child handle's stdout already taken before line 220 — not reachable through normal public API usage.

Common situations: fd exhaustion (too many open files) preventing pipe creation; heavily sandboxed environments (containers/seccomp) that restrict pipe allocation; modified library code.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of Kuberwastaken/claurst@b0637c97ec (2026-09-10). Data as JSON: /api/errors/479f827127519d23. Report an issue: GitHub.

Appendix: source

Thrown at src-rust/crates/core/src/lsp.rs:223

            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());

        let writer = Arc::new(Mutex::new(BufWriter::new(stdin)));
        let pending_clone = pending.clone();
        let diagnostics_clone = diagnostics.clone();
        let server_name = config.name.clone();

        // Consume stderr in the background so the OS pipe buffer never fills up
        if let Some(stderr) = child.stderr.take() {
            let name = server_name.clone();
            tokio::spawn(async move {
                let mut lines = BufReader::new(stderr).lines();
                while let Ok(Some(line)) = lines.next_line().await {
                    tracing::debug!("[LSP SERVER {}] {}", name, line);
                }

View on GitHub (pinned to b0637c97ec)