atuinsh/atuin · error

Failed to read from input

Error message

Failed to read from input

What it means

read_user_password() backs interactive login: when `atuin login` runs without --password, it calls rpassword::prompt_password to read the account password hidden from the terminal. A failed read — no TTY, closed or empty stdin, I/O error — makes the expect panic with 'Failed to read from input'. Other flows reuse it (account delete at delete.rs:28, register).

Source

Thrown at crates/atuin/src/command/client/account/login.rs:352

        let _ = meta.delete_hub_session().await;
    }
    crate::print_error::print_error(
        "Wrong encryption key",
        "The encryption key on this machine does not match the data on the server. \
         You have been logged out.\n\n\
         To fix this, find your existing key by running `atuin key` on a machine that \
         already syncs successfully, then run `atuin login` again here with that key.",
    );
    std::process::exit(1);
}

pub(super) fn or_user_input(value: Option<String>, name: &'static str) -> String {
    value.unwrap_or_else(|| read_user_input(name).unwrap_or_default())
}

pub(super) fn read_user_password() -> String {
    let password = prompt_password("Please enter password: ");
    password.expect("Failed to read from input")
}

/// Returns `None` if stdin reached end of input before a line was read.
fn read_user_input(name: &'static str) -> Option<String> {
    eprint!("Please enter {name}: ");
    get_input().expect("Failed to read from input")
}

#[cfg(test)]
mod tests {
    use atuin_common::encryption::paseto_v4;
    use rstest::rstest;

    #[rstest]
    fn mnemonic_round_trip() {
        let key = paseto_v4::Key::from([
            3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9, 3, 2, 3, 8, 4, 6, 2, 6, 4, 3, 3, 8, 3, 2,
            7, 9, 5,

View on GitHub (pinned to 202f6ad98e)

Solutions

  1. Provide the credentials via flags: atuin login --username "$U" --password "$P" --key "$K"
  2. Allocate a TTY when prompting must run (docker run -it, pty wrappers)
  3. When driving prompts from a pipe, send newline-terminated lines for every prompt (password, key) and keep stdin open

Example fix

# before — panics when stdin has no TTY
atuin login -u alice

# after
atuin login -u alice -p "$ATUIN_PASSWORD" -k "$ATUIN_KEY"
Defensive patterns

Strategy: validation

Validate before calling

use std::io::IsTerminal;

if std::env::args().any(|a| a == "login")
    && !std::env::args().any(|a| a.starts_with("--password") || a == "-p")
    && !std::io::stdin().is_terminal()
{
    eprintln!("atuin login needs --password when stdin is not a TTY");
    std::process::exit(2);
}

Try / catch

let password = match rpassword::prompt_password("Please enter password: ") {
    Ok(pw) => pw,
    Err(e) => {
        eprintln!("cannot read password (stdin/TTY unavailable): {e}; pass --password");
        std::process::exit(2);
    }
};

Prevention

When it happens

Trigger: Running `atuin login` (or register/delete) without -p/--password where no interactive terminal exists: piped or closed stdin, cron, CI, docker without -t.

Common situations: Headless machine setup with `atuin login -u name` but no -p; automation assuming env-based login; containers without an allocated TTY.

Related errors


AI-assisted analysis of atuinsh/atuin@202f6ad98e (2026-08-16). Data as JSON: /api/errors/5ca40bc5d1e02b6c. Report an issue: GitHub.