{"record":{"id":"fa70ac95ef624725","repo":"atuinsh/atuin","slug":"trailing-bytes-in-encoded-kvrecord-malformed","errorCode":null,"errorMessage":"trailing bytes in encoded kvrecord. malformed","messagePattern":"trailing bytes in encoded kvrecord\\. malformed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/atuin-kv/src/store/record.rs","lineNumber":56,"sourceCode":"            eyre!(\"{err:?}\")\n        }\n\n        match version {\n            RecordVersion::V0 => {\n                let mut bytes = decode::Bytes::new(&data.0);\n\n                let nfields = decode::read_array_len(&mut bytes).map_err(error_report)?;\n                ensure!(nfields == 3, \"too many entries in v0 kv record\");\n\n                let bytes = bytes.remaining_slice();\n\n                let (namespace, bytes) =\n                    decode::read_str_from_slice(bytes).map_err(error_report)?;\n                let (key, bytes) = decode::read_str_from_slice(bytes).map_err(error_report)?;\n                let (value, bytes) = decode::read_str_from_slice(bytes).map_err(error_report)?;\n\n                if !bytes.is_empty() {\n                    bail!(\"trailing bytes in encoded kvrecord. malformed\");\n                }\n\n                Ok(Self {\n                    namespace: namespace.to_owned(),\n                    key: key.to_owned(),\n                    value: Some(value.to_owned()),\n                })\n            }\n            RecordVersion::V1 => {\n                let mut bytes = decode::Bytes::new(&data.0);\n\n                let nfields = decode::read_array_len(&mut bytes).map_err(error_report)?;\n                ensure!(nfields == 4, \"too many entries in v1 kv record\");\n\n                let bytes = bytes.remaining_slice();\n\n                let (namespace, bytes) =\n                    decode::read_str_from_slice(bytes).map_err(error_report)?;","sourceCodeStart":38,"sourceCodeEnd":74,"githubUrl":"https://github.com/atuinsh/atuin/blob/900007c48045867e7f44b0f9bc55ad9dbe99ee0d/crates/atuin-kv/src/store/record.rs#L38-L74","documentation":"The KV record decoder for Set records reads namespace, key, and value as length-prefixed strings and requires the payload to be fully consumed. Leftover bytes mean the payload does not match the expected Set-record layout, so decoding fails. This catches corrupt or misaligned binary payloads.","triggerScenarios":"Deserializing a KV Set record whose payload has extra bytes after the value string — corrupted store data, an encoder/decoder layout mismatch, or the wrong payload type passed to the decoder.","commonSituations":"Corrupted SQLite record store; records re-encoded by a different atuin-kv version; accidental decoding of an alias/var/script payload with the KV decoder.","solutions":["Delete the affected KV record and re-set the key so it is re-encoded correctly","Ensure encoder and decoder versions agree on the payload layout","Confirm the record tag/field matches before feeding it to the KV decoder"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Ensure the payload decodes to exactly three strings with no leftovers\n// fn plausible_kv_set(payload: &[u8]) -> bool {\n//     decode::read_str_from_slice(payload).ok()\n//         .and_then(|(_, b)| decode::read_str_from_slice(b).ok())\n//         .and_then(|(_, b)| decode::read_str_from_slice(b).ok())\n//         .map(|(_, rest)| rest.is_empty())\n//         .unwrap_or(false)\n// }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Do not modify record payloads directly in the store","Use the same atuin-kv version to write and read records","Verify the record tag before routing payloads to the KV decoder"],"tags":["deserialization","kv","malformed-data"],"backgroundTag":"schema-validation-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"}