atuinsh/atuin · error

invalid host UUID format in sqlite DB

Error message

invalid host UUID format in sqlite DB

What it means

Panic while converting the `host` column of the record store's `store` table into a Uuid. HostId is the UUID identifying the machine that wrote each record; like the id column it is always a UUID when written by atuin, so a parse failure means the row is corrupt or foreign. The adjacent comment ('pretty fucked so just panic') marks this as intentional fail-fast on store corruption.

Source

Thrown at crates/atuin-client/src/record/sqlite_store.rs:102

        .bind(r.host.id.0.as_hyphenated().to_string())
        .bind(r.tag.as_str())
        .bind(r.timestamp as i64)
        .bind(r.version.as_str())
        .bind(r.data.raw.as_str())
        .bind(r.data.cek.as_str())
        .execute(&mut **tx)
        .await?;

        Ok(())
    }

    fn query_row(row: SqliteRow) -> Record<paseto_v4::EncryptedData> {
        let idx: i64 = row.get("idx");
        let timestamp: i64 = row.get("timestamp");

        // tbh at this point things are pretty fucked so just panic
        let id = Uuid::from_str(row.get("id")).expect("invalid id UUID format in sqlite DB");
        let host = Uuid::from_str(row.get("host")).expect("invalid host UUID format in sqlite DB");

        Record {
            id: RecordId(id),
            idx: idx as u64,
            host: Host::new(HostId(host)),
            timestamp: timestamp as u64,
            tag: RecordTag::from(row.get::<String, _>("tag")),
            version: RecordVersion::from(row.get::<String, _>("version")),
            data: paseto_v4::EncryptedData {
                raw: row.get("data"),
                cek: row.get("cek"),
            },
        }
    }

    async fn load_all(&self) -> Result<Vec<Record<paseto_v4::EncryptedData>>> {
        let res =
            sqlx::query("select * from store ").map(Self::query_row).fetch_all(&self.pool).await?;

View on GitHub (pinned to 15fe1318f1)

Solutions

  1. Confirm which rows are bad: `sqlite3 store.db 'select host, count(*) from store group by host;'` — any value that is not a 36-char UUID-shaped string is the culprit
  2. Restore the store file from backup/snapshot (include the -wal and -shm files), then re-sync from the server
  3. If unrecoverable, move the store aside and let a fresh `atuin sync` repopulate from the record server
  4. Going forward, avoid running multiple atuin variants or external sqlite writers against the same data directory

Example fix

// before (crates/atuin-client/src/record/sqlite_store.rs:102)
let host = Uuid::from_str(row.get("host")).expect("invalid host UUID format in sqlite DB");
// after (propagate as a decode error)
let host = Uuid::from_str(row.get("host"))
    .map_err(|e| sqlx::Error::ColumnDecode { index: "host".into(), source: e.into() })?; // query_row returns Result
Defensive patterns

Strategy: validation

Validate before calling

async fn store_hosts_valid(pool: &sqlx::SqlitePool) -> Result<bool, sqlx::Error> {
    let bad: i64 = sqlx::query_scalar(
        "select count(*) from store where host not glob
         '[0-9a-f]*-[0-9a-f]*-[0-9a-f]*-[0-9a-f]*-[0-9a-f]*' or length(host) != 36",
    )
    .fetch_one(pool)
    .await?;
    Ok(bad == 0)
}

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.all_tagged(&tag))
}));
if res.is_err() {
    // host UUID corruption: stop, snapshot the db, and rebuild the store via re-sync
    tracing::error!("store corrupt (bad host UUID) — snapshot store.db, then re-sync");
}

Prevention

When it happens

Trigger: Any record load — `atuin sync`, `atuin kv`, alias/dotfile/script record reads — encountering a store row whose host value is not a valid UUID. Same corruption vectors as the id column: partial writes, manual edits, wrong/forked binary sharing the data dir, mangled file after disk trouble.

Common situations: Hand-edited or truncated store.db; a crash mid-write leaving a torn WAL frame; sharing ~/.local/share/atuin between a stock atuin and an experimental fork with a different schema; backup restored from a partial copy that dropped trailing bytes.

Related errors


AI-assisted analysis of atuinsh/atuin@15fe1318f1 (2026-08-19). Data as JSON: /api/errors/484db5a5998d1cab. Report an issue: GitHub.