slint-ui/slint · error

Child has no stdin

Error message

Child has no stdin

What it means

Same family as [212] but for stdin: the preview child is spawned with `stdin(Stdio::piped())` and `child.stdin.take()` must yield the pipe. None means the pipe was not configured or already taken. This is an internal invariant; stock builds always pipe stdin.

Solutions

  1. Ensure `.stdin(std::process::Stdio::piped())` is present in the spawn options
  2. Take stdin only once per spawned child; spawn a fresh child for each preview session
  3. Rebuild slint-lsp from a clean checkout
  4. File a bug if reproducible on an unmodified build

Example fix

// before: reusing stale child
let child = &mut self.cached_child;
let mut to_child = child.stdin.take().expect("Child has no stdin");

// after: spawn fresh per session
let mut child = command.spawn()?;
let mut to_child = child.stdin.take().expect("Child has no stdin");
Defensive patterns

Strategy: validation

Validate before calling

let Some(stdin) = child.stdin.take() else { return Err("preview child stdin missing"); };

Type guard

fn has_stdin(child: &std::process::Child) -> bool { child.stdin.is_some() }

Try / catch

// treat a missing pipe as a restartable failure
if child.stdin.is_none() { respawn_preview()?; }

Prevention

When it happens

Trigger: `start_preview` (called from `send`) after the child's stdin handle was already taken, or if the spawn's `.stdin(Stdio::piped())` was dropped/modified.

Common situations: Patched builds changing spawn options; calling start_preview twice on the same Child struct; a bug in preview restart logic reusing handles.

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 slint-ui/slint@bb937076de (2026-09-16). Data as JSON: /api/errors/0d2acb72ddf474f6. Report an issue: GitHub.

Appendix: source

Thrown at internal/editor-preview/child_process.rs:55

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

            if exit_status.map(|exit_status| !exit_status.success()).unwrap_or(true) {

View on GitHub (pinned to bb937076de)