{"record":{"id":"1813dfdca0291792","repo":"atuinsh/atuin","slug":"trailing-bytes-decoding-historyrecord-delete-ma","errorCode":null,"errorMessage":"trailing bytes decoding HistoryRecord::Delete - malformed? got {bytes:?}","messagePattern":"trailing bytes decoding HistoryRecord::Delete - malformed\\? got (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/atuin-client/src/history/store.rs","lineNumber":112,"sourceCode":"            // 0 -> HistoryRecord::Create\n            0 => {\n                // not super useful to us atm, but perhaps in the future\n                // written by write_bin above\n                let _ = decode::read_bin_len(&mut bytes).map_err(error_report)?;\n\n                let record =\n                    History::deserialize(bytes.remaining_slice(), version).map_err(error_report)?;\n\n                Ok(Self::Create(record))\n            }\n\n            // 1 -> HistoryRecord::Delete\n            1 => {\n                let bytes = bytes.remaining_slice();\n                let (id, bytes) = decode::read_str_from_slice(bytes).map_err(error_report)?;\n\n                if !bytes.is_empty() {\n                    bail!(\n                        \"trailing bytes decoding HistoryRecord::Delete - malformed? got {bytes:?}\"\n                    );\n                }\n\n                Ok(Self::Delete(id.parse()?))\n            }\n\n            n => {\n                bail!(\"unknown HistoryRecord type {n}\");\n            }\n        }\n    }\n}\n\n/// How many entries `incremental_build` holds in memory, and the most it puts into a single\n/// `save_bulk`/`delete_rows` transaction.\nconst BUILD_BATCH_SIZE: NonZeroUsize = NonZeroUsize::new(5000).unwrap();\n","sourceCodeStart":94,"sourceCodeEnd":130,"githubUrl":"https://github.com/atuinsh/atuin/blob/c0c717ab04c881764bcad4b3d169a507e2432643/crates/atuin-client/src/history/store.rs#L94-L130","documentation":"While decoding a HistoryRecord tagged as Delete (tag 1), the payload contained bytes left over after reading the record ID. The record store treats this as corruption since Delete records must contain exactly one string field.","triggerScenarios":"Deserializing a binary record whose Delete payload has extra trailing bytes — truncated/corrupted record store files, or records written by an incompatible format version.","commonSituations":"Partially written or damaged SQLite record blobs; syncing records from a newer/older atuin version with format drift.","solutions":["Verify the record store integrity; re-sync from the Hub by deleting the local record DB and re-downloading","Update atuin to the latest version on all machines to align record formats","Restore the record store from backup if corruption persists"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":"fn is_wellformed_delete(payload: &[u8]) -> bool {\n    match decode::read_str_from_slice(payload) {\n    \tOk((_, rest)) => rest.is_empty(),\n    \tErr(_) => false,\n    }\n}","tryCatchPattern":"match HistoryRecord::deserialize(bytes) {\n    Ok(rec) => apply(rec),\n    Err(e) if e.to_string().contains(\"trailing bytes\") => {\n        log::warn!(\"corrupt record skipped: {e}\");\n        // skip or trigger re-sync\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Run identical atuin versions on all synced machines","Shut down cleanly; avoid killing the process mid-write to the record store","Keep backups of the local record DB"],"tags":["serialization","corruption","record-store"],"backgroundTag":"unexpected-response-shape","analyzedSha":"c0c717ab04c881764bcad4b3d169a507e2432643","analyzedAt":"2026-09-12T07:40:01.341Z","contentChangedAt":"2026-09-12T07:40:01.341Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}