{"record":{"id":"f8fb7ecc59cc3b2f","repo":"atuinsh/atuin","slug":"invalid-id-uuid-format-in-sqlite-db","errorCode":null,"errorMessage":"invalid id UUID format in sqlite DB","messagePattern":"invalid id UUID format in sqlite DB","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/atuin-client/src/record/sqlite_store.rs","lineNumber":101,"sourceCode":"        .bind(r.idx as i64)\n        .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 =","sourceCodeStart":83,"sourceCodeEnd":119,"githubUrl":"https://github.com/atuinsh/atuin/blob/15fe1318f1df51de604262eb50734c9883d48e7b/crates/atuin-client/src/record/sqlite_store.rs#L83-L119","documentation":"Panic while converting the `id` column of the record store's `store` table (SQLite) back into a Uuid. The comment above it ('tbh at this point things are pretty fucked so just panic') shows this is a deliberate fail-fast on data corruption: every record written by atuin carries a UUIDv7 id, so a row whose id is not a valid UUID means the store file was corrupted or written by something else. query_row runs on every record load (sync, KV, dotfiles, history v2 reads).","triggerScenarios":"Any record-store read (e.g., `atuin sync`, `atuin store status`, KV/dotfiles operations) hitting a row where store.id is not a parseable UUID: truncated/garbled file from disk issues or crash during write, manual sqlite edits, a store file produced by a divergent fork, or a file replaced with non-atuin data at ~/.local/share/atuin/store.db.","commonSituations":"Users who hand-edited the sqlite store; a full disk or power loss that left a partially written WAL; symlinking ATUIN's data dir to the wrong file; running two incompatible atuin forks against the same data directory.","solutions":["Check whether the store file was manually modified or pointed at the wrong path (ATUIN_DB_PATH / data dir settings) and restore the intended file","Recover from backup: copy the last good store.db (and -wal/-shm) from backups/snapshots, then re-run sync to re-download records from the server","If no backup exists, rename the corrupt store aside and let `atuin sync` rebuild a fresh local store from the remote (local-only records will be lost)","Verify integrity with `sqlite3 ~/.local/share/atuin/store.db 'select id from store where length(id) != 36 or id not like \"%-%-%-%-%\";'` to list the offending rows before deciding to prune them"],"exampleFix":"// before (crates/atuin-client/src/record/sqlite_store.rs:101)\nlet id = Uuid::from_str(row.get(\"id\")).expect(\"invalid id UUID format in sqlite DB\");\n// after (surface corruption as an error instead of aborting the process)\nlet id = Uuid::from_str(row.get(\"id\"))\n    .map_err(|e| sqlx::Error::ColumnDecode { index: \"id\".into(), source: e.into() })?; // query_row returns Result","handlingStrategy":"validation","validationCode":"// Before bulk store reads, scan for rows whose id is not a valid UUID:\nuse uuid::Uuid;\n\nasync fn store_ids_valid(pool: &sqlx::SqlitePool) -> Result<bool, sqlx::Error> {\n    let bad: i64 = sqlx::query_scalar(\n        \"select count(*) from store where id not glob\n         '[0-9a-f]*-[0-9a-f]*-[0-9a-f]*-[0-9a-f]*-[0-9a-f]*' or length(id) != 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":"// Loading records with an external process (e.g., a sync tool) — keep the panic out of your loop:\nlet res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {\n    tokio::task::block_on(store.load(tag, offset, batch))\n}));\nmatch res {\n    Ok(Ok(records)) => { /* process */ }\n    Ok(Err(e)) => return Err(e.into()),\n    Err(_) => {\n        // invalid-UUID panic: quarantine the store file and rebuild from server\n        tracing::error!(\"record store corrupt (bad id UUID) — restore from backup or re-sync\");\n    }\n}","preventionTips":["Keep exclusive, atomic backups of store.db before manual maintenance","Never edit store.db with external sqlite tools","Don't share the atuin data dir between different atuin forks","On abnormal shutdown, run a quick UUID sanity query before the next sync"],"tags":["rust","sqlite","uuid","panic","data-corruption","record-store","sync"],"backgroundTag":"invalid-uuid-string","analyzedSha":"15fe1318f1df51de604262eb50734c9883d48e7b","analyzedAt":"2026-08-19T08:56:57.719Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}