xai-org/grok-build · error
Failed to save credentials: {e}
Error message
Failed to save credentials: {e} What it means
build_auth finishes device-code login by persisting the enriched Auth via auth_manager.update; a persistence failure is wrapped as 'Failed to save credentials'.
Source
Thrown at crates/codegen/xai-grok-shell/src/auth/device_code.rs:492
team_id,
team_name: None,
team_role: None,
user_blocked_reason: None,
team_blocked_reasons: vec![],
coding_data_retention_opt_out: crate::auth::default_coding_data_retention_opt_out(),
has_grok_code_access: None,
refresh_token: tokens.refresh_token.clone(),
expires_at: tokens.expires_in.map(|s| now + Duration::seconds(s)),
oidc_issuer: Some(issuer.to_owned()),
oidc_client_id: Some(client_id.to_owned()),
};
auth_manager.enrich_auth_inline(&mut auth).await;
auth_manager
.update(auth)
.await
.map_err(|e| anyhow::anyhow!("Failed to save credentials: {e}"))
}
/// Decode JWT payload without signature verification.
/// Returns (sub, Option<email>).
fn decode_jwt_claims(jwt: &str) -> (String, Option<String>) {
use base64::Engine;
let parts: Vec<&str> = jwt.splitn(3, '.').collect();
if parts.len() < 2 {
return (String::new(), None);
}
let payload = match base64::engine::general_purpose::URL_SAFE_NO_PAD.decode(parts[1]) {
Ok(bytes) => bytes,
Err(_) => return (String::new(), None),
};
let claims: IdTokenClaims = match serde_json::from_slice(&payload) {
Ok(claims) => claims,
Err(_) => return (String::new(), None),
};View on GitHub (pinned to bc7f02eddd)
Solutions
- Check the underlying update error in the message chain for the concrete cause (permissions, keyring, disk)
- Verify the credentials file/directory is writable (or the OS keyring is reachable in headless environments)
- Set an explicit credentials-file path the process can write if the default location is restricted
- Ensure no concurrent login processes are racing on the same store
Example fix
// before XDG_DATA_HOME unset -> keyring missing in headless CI // after export XDG_DATA_HOME=/tmp/xdg # writable file-backed credential store <run device-code login again>
Defensive patterns
Strategy: try-catch
Validate before calling
// pre-check storage writability let dir = credentials_dir()?; assert!(dir.exists() && !dir.permissions().readonly(), "credential store not writable");
Try / catch
match build_auth(...).await {
Err(e) if e.to_string().contains("Failed to save credentials") => {
eprintln!("persisting auth failed: {e}; check keyring/permissions");
// fall back to a file-based credentials path
}
other => other?,
} Prevention
- Provision a writable credentials path (or keyring service) in CI/containers before login
- Avoid concurrent logins against the same credential store
- Surface chained error causes (source) when logging this failure
When it happens
Trigger: auth_manager.update returning Err — credential store unwritable, keychain/keyring unavailable, locked or corrupt credentials file, or storage backend rejecting the update.
Common situations: Read-only home dir or disk full in CI containers, headless Linux without a keyring service, file permissions denying access to the credentials path, concurrent login runs corrupting the store.
Related errors
- failed to save external auth credentials: {e}
- upload parked: credentials rejected (HTTP 401); retrying in
- Failed to clear auth: {e}
- Error: Session ID {session_id} is already in use.
- The change was applied, but Doctor could not verify `{}` in
AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31).
Data as JSON: /api/errors/d9dcf3e3be363078.
Report an issue: GitHub.