zeroclaw-labs/zeroclaw · error · anyhow::Error

No input received from stdin

Error message

No input received from stdin

What it means

interact_text_with_io writes the prompt, flushes, then reads one line from stdin. read_line returning 0 bytes is EOF (stream closed — not the same as an empty line), and the helper bails immediately. Note the configured .default() value only applies to a blank line; EOF never falls back to the default.

Source

Thrown at crates/zeroclaw-runtime/src/cli_input.rs:136

    pub fn interact_text(self) -> Result<String> {
        let stdin = std::io::stdin();
        let stdout = std::io::stdout();
        self.interact_text_with_io(stdin.lock(), stdout.lock())
    }

    fn interact_text_with_io<R: BufRead, W: Write>(
        self,
        mut reader: R,
        mut writer: W,
    ) -> Result<String> {
        loop {
            write!(writer, "{}", self.render_prompt())?;
            writer.flush()?;

            let mut line = String::new();
            let bytes_read = reader.read_line(&mut line)?;
            if bytes_read == 0 {
                bail!("No input received from stdin");
            }

            let trimmed = trim_trailing_line_ending(&line);
            if trimmed.is_empty() {
                if let Some(default) = &self.default {
                    return Ok(default.clone());
                }
                if self.allow_empty {
                    return Ok(String::new());
                }
                writeln!(writer, "Input cannot be empty.")?;
                continue;
            }

            return Ok(trimmed.to_string());
        }
    }

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Attach a TTY or provide input on stdin when the command prompts
  2. Handle this error at the call site and substitute your own fallback on EOF
  3. When scripting and you want the configured default, pipe a newline (blank line) instead of closing stdin
  4. For non-interactive flows, pass values via flags or config instead of interactive prompts

Example fix

// before
let value = prompt.interact_text()?; // Ctrl-D aborts the whole program

// after
let value = match prompt.interact_text() {
    Ok(v) => v,
    Err(e) if e.to_string().contains("No input received from stdin") => fallback.clone(),
    Err(e) => return Err(e),
};
Defensive patterns

Strategy: try-catch

Validate before calling

use std::io::IsTerminal;
if !std::io::stdin().is_terminal() {
    // non-interactive context: take values from flags/config instead of prompting
    return Ok(config_value);
}

Try / catch

let value = match prompt.interact_text() {
    Ok(v) => v,
    Err(ref e) if e.to_string().contains("No input received from stdin") => fallback.clone(),
    Err(e) => return Err(e),
};

Prevention

When it happens

Trigger: User presses Ctrl-D at the prompt; a script pipes zero bytes then closes stdin (e.g. `true | zeroclaw ...`); stdin was already exhausted by an earlier prompt in the same process.

Common situations: CLI invoked non-interactively in CI/cron/piped contexts without input; here-docs consumed by a previous read; test harnesses that close stdin; dropped ssh sessions.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/8b3789952856f842. Report an issue: GitHub.