Kuberwastaken/claurst · error · anyhow::Error
LSP server stdin not available
Error message
LSP server stdin not available
What it means
After spawning the LSP server process, `LspClient::start` calls `child.stdin.take()` to grab the piped stdin handle; it was `None`. Since `start` always configures `Stdio::piped()` for stdin, this indicates the process handle's stdio was already taken or the child was constructed unexpectedly — an internal invariant violation rather than a user-config error.
Solutions
- Retry process startup — transient fd/resource exhaustion is the realistic cause.
- Check system fd limits (`ulimit -n`) and close leaking file descriptors in the host process.
- If reproducible, inspect for concurrent use/mutation of the same Child handle.
- Report as a bug if it reproduces on a normal system; it violates the library's invariant.
Defensive patterns
Strategy: retry
Try / catch
loop {
match LspClient::start(config.clone()).await {
Ok(c) => break c,
Err(e) if attempts < 3 && e.to_string().contains("stdin not available") => { attempts += 1; tokio::time::sleep(Duration::from_millis(200)).await; }
Err(e) => return Err(e),
}
} Prevention
- Don't take stdin/stdout from the Child yourself before LspClient::start does.
- Keep system fd usage in check; monitor open descriptors in long-running hosts.
- Treat this as an internal bug and report it if it reproduces.
When it happens
Trigger: Practically unreachable through the public API: `start` always sets `stdin(Stdio::piped())` immediately before spawn. It could only occur if stdio pipes failed to be created by the OS at spawn time or the Child handle was mutated between spawn and the take() call.
Common situations: Rare OS-level pipe-creation failure under resource exhaustion (fd limits); fork/exec edge cases on constrained systems; code modifications that take stdin before this point.
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
- LSP server stdout not available
- LSP server closed stdout
- LSP message missing Content-Length header
- Failed to start LSP server
- LSP client already shut down
AI-assisted analysis of Kuberwastaken/claurst@b0637c97ec (2026-09-10).
Data as JSON: /api/errors/d69ad250985105af.
Report an issue: GitHub.
Appendix: source
Thrown at src-rust/crates/core/src/lsp.rs:219
// 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());
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 {
View on GitHub (pinned to b0637c97ec)