atuinsh/atuin · error
failed to parse uuid for local store status
Error message
failed to parse uuid for local store status
What it means
Panic while building the local record-store status: the query `select host, tag, max(idx) from store group by host, tag` succeeds, then each host string is parsed back into a Uuid. Because every store row's host is a UUID at write time, a parse failure means at least one host group in the table holds non-UUID text — store corruption. Unlike query_row's fail-fast comment, this site is inside a function returning Result, yet still uses expect.
Solutions
- List the offending host values: `sqlite3 ~/.local/share/atuin/store.db 'select host, count(*) from store group by host;'` and look for non-UUID strings
- Restore the store from backup, or delete just the bad rows (`delete from store where host = '<bad value>';`) if the rest is intact, then re-run sync
- If the store is broadly damaged, move it aside and rebuild the local store via `atuin sync` from the server
- When patching the crate, replace the expect with `let host = Uuid::from_str(&i.0).wrap_err("invalid host in local store status")?;` since the enclosing function already returns Result
Example fix
// before (crates/atuin-client/src/record/sqlite_store.rs:305)
let host = HostId(Uuid::from_str(i.0.as_str()).expect("failed to parse uuid for local store status"));
// after
let host = HostId(Uuid::from_str(i.0.as_str()).wrap_err("failed to parse uuid for local store status")?); Defensive patterns
Strategy: validation
Validate before calling
async fn status_hosts_valid(pool: &sqlx::SqlitePool) -> Result<bool, sqlx::Error> {
let rows: Vec<(String, i64)> = sqlx::query_as(
"select host, count(*) from store group by host",
)
.fetch_all(pool)
.await?;
Ok(rows.iter().all(|(host, _)| uuid::Uuid::parse_str(host).is_ok()))
} Type guard
fn is_valid_uuid(s: &str) -> bool {
uuid::Uuid::parse_str(s).is_ok()
} Try / catch
let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
tokio::task::block_on(store.status())
}));
match res {
Ok(Ok(status)) => { /* compute sync tail */ }
Ok(Err(e)) => return Err(e.into()),
Err(_) => tracing::error!("status build hit a bad host UUID — prune bad rows and re-sync"),
} Prevention
- Pre-check grouped host values before running sync status on a store you did not write
- Delete bad rows selectively rather than wiping the whole store
- Keep server-side records as the source of truth so a local store can always be rebuilt
When it happens
Trigger: Calling the local store status builder (used by `atuin sync` status calculation and store status commands) when the store table contains any host value that is not a UUID. One corrupt row anywhere in the table poisons the whole status computation because the GROUP BY aggregates all hosts.
Common situations: Corrupt or externally modified store.db; a torn write after power loss; a fork or script having inserted rows with arbitrary host strings; restored-from-partial-backup data directories.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- invalid host UUID format in sqlite DB
- invalid id UUID format in sqlite DB
- issue in stats average query
- issue in stats day of week query
- issue in stats duration over time query
AI-assisted analysis of atuinsh/atuin@15fe1318f1 (2026-08-19).
Data as JSON: /api/errors/6087eb6e50d7e253.
Report an issue: GitHub.
Appendix: source
Thrown at crates/atuin-client/src/record/sqlite_store.rs:305
}
}
pub async fn status(&self) -> Result<RecordStatus> {
let mut status = RecordStatus::new();
let res: Result<Vec<(String, String, i64)>, sqlx::Error> =
sqlx::query_as("select host, tag, max(idx) from store group by host, tag")
.fetch_all(&self.pool)
.await;
let res = match res {
Err(e) => return Err(eyre!("failed to fetch local store status: {}", e)),
Ok(v) => v,
};
for i in res {
let host = HostId(
Uuid::from_str(i.0.as_str()).expect("failed to parse uuid for local store status"),
);
status.set_raw(host, RecordTag::from(i.1), i.2 as u64);
}
Ok(status)
}
pub async fn all_tagged(
&self,
tag: &RecordTag,
) -> Result<Vec<Record<paseto_v4::EncryptedData>>> {
let res = sqlx::query("select * from store where tag = ?1 order by timestamp asc")
.bind(tag.as_str())
.map(Self::query_row)
.fetch_all(&self.pool)
.await?;
View on GitHub (pinned to 15fe1318f1)