atuinsh/atuin · error

Failed to read from input

Error message

Failed to read from input

What it means

When `atuin account change-password` runs without --current-password, it prompts via rpassword::prompt_password, which reads a hidden password from the terminal. If the read fails — stdin closed (EOF, /dev/null), no controlling TTY, or an I/O error — the expect panics with 'Failed to read from input'.

Source

Thrown at crates/atuin/src/command/client/account/change_password.rs:33

    #[clap(long, short)]
    pub new_password: Option<String>,

    /// The two-factor authentication code for your account, if any
    #[clap(long, short)]
    pub totp_code: Option<String>,
}

impl Cmd {
    pub async fn run(&self, settings: &Settings) -> Result<()> {
        if !settings.logged_in().await? {
            bail!("You are not logged in");
        }

        let client = auth::auth_client(settings).await;

        let current_password = self.current_password.clone().unwrap_or_else(|| {
            prompt_password("Please enter the current password: ")
                .expect("Failed to read from input")
        });

        if current_password.is_empty() {
            bail!("please provide the current password");
        }

        let new_password = self.new_password.clone().unwrap_or_else(|| {
            prompt_password("Please enter the new password: ").expect("Failed to read from input")
        });

        if new_password.is_empty() {
            bail!("please provide a new password");
        }

        let mut totp_code = self.totp_code.clone();

        loop {
            let response = client

View on GitHub (pinned to 202f6ad98e)

Solutions

  1. Pass the values as flags: atuin account change-password --current-password "$OLD" --new-password "$NEW"
  2. Run the command in an interactive terminal (docker run -it / docker exec -it)
  3. If driving the prompt from a pipe, supply a newline-terminated line and keep stdin open until the command exits

Example fix

# before — panics: no TTY, stdin empty
atuin account change-password < /dev/null

# after — headless flags
atuin account change-password --current-password "$OLD" --new-password "$NEW"
Defensive patterns

Strategy: validation

Validate before calling

use std::io::IsTerminal;

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

Try / catch

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

Prevention

When it happens

Trigger: Running `atuin account change-password` without --current-password in a non-interactive context: stdin redirected from /dev/null or an exhausted pipe, or no TTY attached (cron, CI, docker run/exec without -t).

Common situations: Automation scripts that forgot the flag; CI pipelines; docker exec into containers without a TTY; piping empty input into atuin.

Related errors


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