Schniz/fnm · error

Can't read user input

Error message

Can't read user input

What it means

When `fnm use <version>` cannot find the requested installed version, it prints a colored install prompt and reads one line from stdin. If `read_line` returns an io error — stdin closed (EBADF), redirected to something unreadable, or an I/O failure — `.expect("Can't read user input")` panics. Plain EOF (e.g. stdin from /dev/null) is not an error: it leaves the answer empty, which counts as 'no'.

Source

Thrown at src/commands/use.rs:194

fn should_install_interactively(requested_version: &UserVersion) -> bool {
    use std::io::{IsTerminal, Write};

    if !(std::io::stdout().is_terminal() && std::io::stdin().is_terminal()) {
        return false;
    }

    let error_message = format!(
        "fnm can't find an installed Node version matching {}.",
        requested_version.to_string().italic()
    );
    eprintln!("{}", error_message.red());
    let do_you_want = format!("Do you want to install it? {} [y/N]:", "answer".bold());
    eprint!("{} ", do_you_want.yellow());
    std::io::stdout().flush().unwrap();
    let mut s = String::new();
    std::io::stdin()
        .read_line(&mut s)
        .expect("Can't read user input");

    s.trim().to_lowercase() == "y"
}

fn warn_if_multishell_path_not_in_path_env_var(
    multishell_path: &std::path::Path,
    config: &FnmConfig,
) {
    if let Some(path_var) = std::env::var_os("PATH") {
        let bin_path = if cfg!(unix) {
            multishell_path.join("bin")
        } else {
            multishell_path.to_path_buf()
        };

        let fixed_path = bin_path.to_str().and_then(shell::maybe_fix_windows_path);
        let fixed_path = fixed_path.as_deref();

View on GitHub (pinned to 86adc9676c)

Solutions

  1. Pre-install so the prompt never appears: `fnm install 18 && fnm use 18`.
  2. Give stdin a safe fallback that declines gracefully: `fnm use 18 </dev/null` (EOF → answer 'no').
  3. In non-interactive contexts, prefer `fnm use --version-file` flows or set FNM_VERSION_FILE_STRATEGY and ensure versions are pre-installed.
  4. If patching fnm: treat a read error as 'no' instead of panicking — `.unwrap_or_default()` on the read result.

Example fix

// before (src/commands/use.rs)
std::io::stdin().read_line(&mut s).expect("Can't read user input");

// after
let _ = std::io::stdin().read_line(&mut s); // read failure => empty answer => "no"
Defensive patterns

Strategy: validation

Validate before calling

# only attempt interactive fnm use when stdin is readable
if [ -t 0 ] || [ -r /dev/stdin ]; then
  fnm use "$VERSION" </dev/tty 2>/dev/null || fnm use "$VERSION"
else
  fnm install "$VERSION" && fnm use "$VERSION"  # non-interactive path, no prompt
fi

Try / catch

let mut s = String::new();
match std::io::stdin().read_line(&mut s) {
    Ok(_) => s.trim().eq_ignore_ascii_case("y"),
    Err(_) => false, // unreadable stdin => treat as "no", never panic
}

Prevention

When it happens

Trigger: Running `fnm use <missing-version>` in a context where stdin is closed or not open for reading — `fnm use 18 0<&-`, stdin redirected to a directory, or fnm invoked from a daemon/scheduled task/CI job with no console attached.

Common situations: CI pipelines and cron jobs calling `fnm use` without pre-installing the version; scripts run with detached stdin; IDE external tasks with no terminal; `fnm use` inside hooks (pre-commit, direnv) where stdin is consumed or closed.

Related errors


AI-assisted analysis of Schniz/fnm@86adc9676c (2026-08-16). Data as JSON: /api/errors/79badc702802ea42. Report an issue: GitHub.