slint-ui/slint · error
Child has no stdout
Error message
Child has no stdout
What it means
The editor-preview LSP spawns its preview child process with `stdout(Stdio::piped())`, then takes ownership of the stdout pipe. `child.stdout.take()` returning None means the pipe was not configured or already taken — an internal invariant break in the spawn setup. The panic aborts the LSP server's preview thread.
Solutions
- Ensure the spawn call keeps `.stdout(std::process::Stdio::piped())`
- Take stdout exactly once and keep the returned reader alive
- Rebuild/reinstall the slint-lsp binary from a clean checkout
- Report a bug with version info if reproducible in an unmodified build
Example fix
// before (stdout taken twice)
let a = child.stdout.take().expect("Child has no stdout");
let b = child.stdout.take().expect("Child has no stdout");
// after
let a = child.stdout.take().expect("Child has no stdout");
// do not take again; use `a` Defensive patterns
Strategy: validation
Validate before calling
let Some(stdout) = child.stdout.take() else { return Err("preview child stdout missing"); }; Type guard
fn has_pipes(child: &std::process::Child) -> bool { child.stdout.is_some() && child.stdin.is_some() } Try / catch
// wrap start_preview
match start_preview().await {
Ok(h) => h,
Err(e) => tracing::error!("preview failed to start: {e}"),
} Prevention
- Always spawn with Stdio::piped() for both stdin and stdout
- Take each pipe handle exactly once, immediately after spawn
- Spawn a fresh Child per preview session; never reuse handles
When it happens
Trigger: `start_preview` (called from `send`) spawning the preview binary when the `Stdio::piped()` configuration did not take effect or stdout was already consumed — practically only from a code regression in spawn options or an already-taken handle.
Common situations: Custom/patched builds where spawn options were modified; reusing a `Child` whose stdout was already taken; extremely unlikely in stock builds.
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
- Child has no stdin
- Could not find executable name of the slint-lsp
- Identifier is in unknown document
- URL conversion can not fail here
- Callbacks were set up earlier
AI-assisted analysis of slint-ui/slint@bb937076de (2026-09-16).
Data as JSON: /api/errors/df4512cc3fa51b70.
Report an issue: GitHub.
Appendix: source
Thrown at internal/editor-preview/child_process.rs:54
fn preview_is_running(&self) -> bool {
self.inner.borrow().as_ref().map(|i| !i.communication_handle.is_finished()).unwrap_or(false)
}
fn start_preview(&self) -> crate::Result<()> {
if let Some(inner) = self.inner.borrow_mut().take() {
inner.communication_handle.abort();
}
let mut child = tokio::process::Command::new(&self.executable)
.args(&self.arguments)
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.spawn()?;
tracing::debug!("Preview process spawned (PID {:?})", child.id());
let from_child = child.stdout.take().expect("Child has no stdout");
let mut to_child = child.stdin.take().expect("Child has no stdin");
let channel = self.preview_to_lsp_channel.clone();
let communication_handle = tokio::spawn(async move {
let _exited_guard = scopeguard::guard(channel.clone(), |channel| {
channel.send(PreviewToLspMessage::Exited).ok();
});
let reader = tokio::io::BufReader::new(from_child);
let mut lines = reader.lines();
while let Some(line) = lines.next_line().await.map_err(|e| e.to_string())? {
if let Ok(message) = serde_json::from_str(&line) {
channel.send(message).map_err(|e| e.to_string())?;
}
}
let exit_status = child.wait().await.map_err(|e| e.to_string());
View on GitHub (pinned to bb937076de)