atuinsh/atuin · error
Hub login failed with status
Error message
Hub login failed with status {status} What it means
Final fallback in `HubAuthClient::login`: any non-success status that is not 403-with-parseable-body and not 401 becomes this bail embedding the HTTP status. It covers 3xx redirections, 404, 429, and all 5xx responses from the Hub or intermediaries.
Solutions
- Check status embedded in the message: 4xx means fix the request/URL, 5xx means server-side
- Verify the configured Hub address is correct and reachable (`curl https://api.atuin.sh/api/v0/login`)
- Wait and retry with backoff if the status was 429 or 5xx (check https://status for Hub incidents)
- Bypass proxies/VPN to rule out intermediary interference
- Update the Atuin CLI in case the login API path changed
Example fix
// before: opaque status-only bail
bail!("Hub login failed with status {status}");
// after: surface response body for diagnosis
let body = resp.text().await.unwrap_or_default();
bail!("Hub login failed with status {status}: {body}"); Defensive patterns
Strategy: retry
Validate before calling
// Check Hub reachability before login
let status = reqwest::get("https://api.atuin.sh/health").await?.status();
if !status.is_success() {
bail!("Hub unreachable/unhealthy: {status}");
} Type guard
fn is_unmapped_login_status(err: &Report) -> Option<u16> {
let msg = err.to_string();
msg.strip_prefix("Hub login failed with status ")
.and_then(|s| s.split(' ').next())
.and_then(|code| code.parse().ok())
} Try / catch
match hub.login(username, password, totp).await {
Err(e) if e.to_string().starts_with("Hub login failed with status") => {
let retryable = e.to_string().contains("429")
|| e.to_string().contains("5"); // 5xx
if retryable {
tokio::time::sleep(Duration::from_secs(10)).await;
// retry with backoff
} else {
eprintln!("fix Hub address/request: {e}");
}
}
other => { /* handle normally */ }
} Prevention
- Validate the configured Hub address resolves to the real API host
- Retry 429/5xx with exponential backoff rather than immediate repeats
- Watch Hub status/announcements during outages
- Exclude corporate proxies/VPNs when diagnosing unexpected login statuses
When it happens
Trigger: POST `api/v0/login` returns e.g. 404 (wrong base URL/path), 429 (rate limited), 500/502/503 (Hub or proxy outage), or an unexpected redirect.
Common situations: Misconfigured Hub address (wrong host/path producing 404); Hub service disruption (5xx); aggressive rate limiting after repeated failed logins; corporate proxy or captive portal intercepting the request.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
- {}
- invalid credentials
- Hub account deletion failed with status
- Hub password change failed with status
- Hub registration failed with status
AI-assisted analysis of atuinsh/atuin@900007c480 (2026-09-19).
Data as JSON: /api/errors/66787f59b12aa306.
Report an issue: GitHub.
Appendix: source
Thrown at crates/atuin-client/src/auth.rs:319
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,
}))
.send()
.await
.context("failed to connect to Atuin Hub")?;View on GitHub (pinned to 900007c480)