{"record":{"id":"484db5a5998d1cab","repo":"atuinsh/atuin","slug":"invalid-host-uuid-format-in-sqlite-db","errorCode":null,"errorMessage":"invalid host UUID format in sqlite DB","messagePattern":"invalid host UUID format in sqlite DB","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/atuin-client/src/record/sqlite_store.rs","lineNumber":102,"sourceCode":"        .bind(r.host.id.0.as_hyphenated().to_string())\n        .bind(r.tag.as_str())\n        .bind(r.timestamp as i64)\n        .bind(r.version.as_str())\n        .bind(r.data.raw.as_str())\n        .bind(r.data.cek.as_str())\n        .execute(&mut **tx)\n        .await?;\n\n        Ok(())\n    }\n\n    fn query_row(row: SqliteRow) -> Record<paseto_v4::EncryptedData> {\n        let idx: i64 = row.get(\"idx\");\n        let timestamp: i64 = row.get(\"timestamp\");\n\n        // tbh at this point things are pretty fucked so just panic\n        let id = Uuid::from_str(row.get(\"id\")).expect(\"invalid id UUID format in sqlite DB\");\n        let host = Uuid::from_str(row.get(\"host\")).expect(\"invalid host UUID format in sqlite DB\");\n\n        Record {\n            id: RecordId(id),\n            idx: idx as u64,\n            host: Host::new(HostId(host)),\n            timestamp: timestamp as u64,\n            tag: RecordTag::from(row.get::<String, _>(\"tag\")),\n            version: RecordVersion::from(row.get::<String, _>(\"version\")),\n            data: paseto_v4::EncryptedData {\n                raw: row.get(\"data\"),\n                cek: row.get(\"cek\"),\n            },\n        }\n    }\n\n    async fn load_all(&self) -> Result<Vec<Record<paseto_v4::EncryptedData>>> {\n        let res =\n            sqlx::query(\"select * from store \").map(Self::query_row).fetch_all(&self.pool).await?;","sourceCodeStart":84,"sourceCodeEnd":120,"githubUrl":"https://github.com/atuinsh/atuin/blob/15fe1318f1df51de604262eb50734c9883d48e7b/crates/atuin-client/src/record/sqlite_store.rs#L84-L120","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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","Restore the store file from backup/snapshot (include the -wal and -shm files), then re-sync from the server","If unrecoverable, move the store aside and let a fresh `atuin sync` repopulate from the record server","Going forward, avoid running multiple atuin variants or external sqlite writers against the same data directory"],"exampleFix":"// before (crates/atuin-client/src/record/sqlite_store.rs:102)\nlet host = Uuid::from_str(row.get(\"host\")).expect(\"invalid host UUID format in sqlite DB\");\n// after (propagate as a decode error)\nlet host = Uuid::from_str(row.get(\"host\"))\n    .map_err(|e| sqlx::Error::ColumnDecode { index: \"host\".into(), source: e.into() })?; // query_row returns Result","handlingStrategy":"validation","validationCode":"async fn store_hosts_valid(pool: &sqlx::SqlitePool) -> Result<bool, sqlx::Error> {\n    let bad: i64 = sqlx::query_scalar(\n        \"select count(*) from store where host not glob\n         '[0-9a-f]*-[0-9a-f]*-[0-9a-f]*-[0-9a-f]*-[0-9a-f]*' or length(host) != 36\",\n    )\n    .fetch_one(pool)\n    .await?;\n    Ok(bad == 0)\n}","typeGuard":"fn is_valid_uuid(s: &str) -> bool {\n    uuid::Uuid::parse_str(s).is_ok()\n}","tryCatchPattern":"let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {\n    tokio::task::block_on(store.all_tagged(&tag))\n}));\nif res.is_err() {\n    // host UUID corruption: stop, snapshot the db, and rebuild the store via re-sync\n    tracing::error!(\"store corrupt (bad host UUID) — snapshot store.db, then re-sync\");\n}","preventionTips":["Back up the whole data dir (db + wal + shm) atomically, not just store.db","Avoid force-killing atuin mid-sync; use its normal shutdown","Validate host/id columns after restoring from any partial backup"],"tags":["rust","sqlite","uuid","panic","data-corruption","record-store","host-id"],"backgroundTag":"invalid-uuid-string","analyzedSha":"15fe1318f1df51de604262eb50734c9883d48e7b","analyzedAt":"2026-08-19T08:56:57.719Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}