atuinsh/atuin · error

invalid credentials

Error message

invalid credentials

What it means

In `HubAuthClient::login`, an HTTP 401 (UNAUTHORIZED) response is mapped to this fixed bail. 401 means the supplied username/password combination failed authentication on the Hub. The client deliberately does not parse the body and reports 'invalid credentials'.

Solutions

  1. Re-enter username and password carefully, checking for autocorrect/whitespace issues
  2. Verify the account exists on Atuin Hub (register first if not) and that you're logging into the right service
  3. Reset the password via the Hub web UI if it may have changed
  4. If 2FA is enabled on the account, run login again and provide the TOTP code when prompted
  5. Confirm you're not mixing credentials from a self-hosted server with the public Hub

Example fix

null
Defensive patterns

Strategy: validation

Validate before calling

// Basic client-side sanity checks before hitting the Hub
if username.trim().is_empty() || password.is_empty() {
    bail!("username and password must be non-empty");
}
// Optionally verify credentials independently before login

Type guard

fn is_invalid_credentials(err: &Report) -> bool {
    err.to_string() == "invalid credentials"
}

Try / catch

match hub.login(username, password, totp).await {
    Ok(AuthResponse::Success { .. }) => println!("logged in"),
    Err(e) if e.to_string() == "invalid credentials" => {
        eprintln!("check username/password; reset via Hub web UI if forgotten");
    }
    Ok(AuthResponse::TwoFactorRequired) => { /* collect TOTP and retry */ }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: POST `api/v0/login` with a wrong username, wrong password, or a username that doesn't exist (server answers 401 for both to avoid user enumeration).

Common situations: Typo'd username or password in `atuin login`; credentials from a password manager out of date after a recent change; attempting login on Atuin Hub with a local-only/self-hosted account; case-sensitivity mistakes in username.

Understand the failure class

Related errors


AI-assisted analysis of atuinsh/atuin@900007c480 (2026-09-19). Data as JSON: /api/errors/586b576efa2de545. Report an issue: GitHub.

Appendix: source

Thrown at crates/atuin-client/src/auth.rs:316

        if status.is_success() {
            let login: LoginResponse = resp.json().await?;
            return Ok(AuthResponse::Success {
                session: login.session,
                auth_type: login.auth,
            });
        }

        if status == StatusCode::FORBIDDEN
            && let Ok(err) = resp.json::<HubErrorResponse>().await
        {
            if err.code.as_deref() == Some("2fa_required") {
                return Ok(AuthResponse::TwoFactorRequired);
            }
            bail!("{}", err.reason);
        }

        if status == StatusCode::UNAUTHORIZED {
            bail!("invalid credentials");
        }

        bail!("Hub login failed with status {status}");
    }

    async fn register(&self, username: &str, email: &str, password: &str) -> Result<AuthResponse> {
        let url = self.address.append_path("api/v0/register")?;
        let client = reqwest::Client::new();

        let resp = client
            .post(url)
            .header(USER_AGENT, APP_USER_AGENT)
            .header(ATUIN_HEADER_VERSION, ATUIN_CARGO_VERSION)
            .json(&serde_json::json!({
                "email": email,
                "username": username,
                "password": password,
            }))

View on GitHub (pinned to 900007c480)