{"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/900007c48045867e7f44b0f9bc55ad9dbe99ee0d/crates/atuin-client/src/history/store.rs#L94-L130","documentation":"`HistoryRecord::deserialize` (atuin-client/src/history/store.rs) decodes record-type 1 (Delete) as a single string id read via `read_str_from_slice`. If the remaining buffer still has bytes after reading the id, the record framing is malformed, so it bails. This is a corruption/protocol-safety check on binary record payloads received from sync.","triggerScenarios":"Deserializing a history record whose type byte is 1 but whose payload contains extra bytes after the string id — e.g. truncated-then-resumed sync streams, a record written by an incompatible record version, or corrupted local record store data.","commonSituations":"Syncing against a server/peer producing records in a newer format; interrupted sync leaving partial data; manually edited or corrupted `~/.local/share/atuin` record database.","solutions":["Identify the offending record and delete it from the record store so sync can re-fetch a clean copy","Back up and inspect the record DB (`~/.local/share/atuin/records.db` or similar) for corruption","Update atuin on all syncing machines to matching versions to eliminate format skew","As a last resort, reset the record store and re-sync from the server"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"match record_decode_result {\n    Err(e) if e.to_string().contains(\"trailing bytes decoding HistoryRecord::Delete\") => {\n        // quarantine the record id and re-sync it\n    }\n    other => other?,\n}","preventionTips":["Keep atuin versions identical across all sync devices","Never manually edit the record store database","Back up ~/.local/share/atuin before risky operations","Let interrupted syncs resume rather than force-killing them"],"tags":["serialization","sync","record-store"],"backgroundTag":"protobuf-unmarshal-failed","analyzedSha":"900007c48045867e7f44b0f9bc55ad9dbe99ee0d","analyzedAt":"2026-09-19T12:52:51.491Z","contentChangedAt":"2026-09-19T12:52:51.491Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}