{"record":{"id":"9cdcddce087fa151","repo":"atuinsh/atuin","slug":"trailing-bytes-in-encoded-dotfiles-env-record-mal","errorCode":null,"errorMessage":"trailing bytes in encoded dotfiles env record, malformed","messagePattern":"trailing bytes in encoded dotfiles env record, malformed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/atuin-dotfiles/src/shell.rs","lineNumber":65,"sourceCode":"\n        let nfields = decode::read_array_len(bytes).map_err(error_report)?;\n\n        ensure!(\n            nfields == 3,\n            \"too many entries in v0 dotfiles env create record, got {}, expected {}\",\n            nfields,\n            3\n        );\n\n        let bytes = bytes.remaining_slice();\n\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        let mut bytes = decode::Bytes::new(bytes);\n        let export = decode::read_bool(&mut bytes).map_err(error_report)?;\n\n        ensure!(\n            bytes.remaining_slice().is_empty(),\n            \"trailing bytes in encoded dotfiles env record, malformed\"\n        );\n\n        Ok(Self {\n            name: key.to_owned(),\n            value: value.to_owned(),\n            export,\n        })\n    }\n}\n\n#[must_use]\npub fn parse_alias(line: &str) -> Option<Alias> {\n    // consider the fact we might be importing a fish alias\n    // 'alias' output\n    // fish: alias foo bar\n    // posix: foo=bar","sourceCodeStart":47,"sourceCodeEnd":83,"githubUrl":"https://github.com/atuinsh/atuin/blob/900007c48045867e7f44b0f9bc55ad9dbe99ee0d/crates/atuin-dotfiles/src/shell.rs#L47-L83","documentation":"Atuin's dotfiles env record deserializer reads a key, a value, and a bool export flag from the record payload, then asserts no bytes remain. Leftover trailing bytes mean the payload has more fields than the v0 env record schema defines, so the data is malformed or was written by an incompatible encoder. Deserialization is refused rather than silently ignoring the extra data.","triggerScenarios":"Calling the env record's deserialize on a payload whose MsgPack body contains extra fields after `name`, `value`, and `export` — e.g. a record encoded by a newer schema version, a hand-crafted or corrupted record, or a bug in a custom encoder.","commonSituations":"Syncing dotfiles between Atuin versions where the env record format changed, records tampered with or truncated/corrupted in the record store, or third-party tooling writing env records directly to the record store.","solutions":["Re-create the affected env record with your current Atuin version: delete it (`atuin dotfiles env delete <name>`) and re-set it (`atuin dotfiles env set <name> <value>`).","Identify the encoder that produced the record; if it is a custom/third-party writer, fix it to emit exactly the three fields in the documented order.","Check for an Atuin version mismatch between sync peers and upgrade all clients to the same version.","Inspect the raw record payload (MsgPack decode it) to confirm how many fields it carries before assuming the record is salvageable."],"exampleFix":"// before: writing a record with an extra field\nlet bytes = encode(name, value, export, extra_field);\n\n// after: encode exactly the fields the v0 schema defines\nlet bytes = encode::to_bytes(&(name, value, export));","handlingStrategy":"validation","validationCode":"// MsgPack-decode the payload first and check it has exactly 3 elements\n// before handing it to deserialize:\nlet value: rmpv::Value = rmpv::decode::read_value(&mut cursor)?;\nassert_eq!(value.as_array().map(|a| a.len()), Some(3), \"env record must have name, value, export\");","typeGuard":"fn is_well_formed_env_record(payload: &[u8]) -> bool {\n    rmpv::decode::read_value(&mut std::io::Cursor::new(payload))\n        .ok()\n        .and_then(|v| v.as_array().map(|a| a.len() == 3))\n        .unwrap_or(false)\n}","tryCatchPattern":"match ShellEnv::deserialize(&bytes) {\n    Ok(env) => apply(env),\n    Err(e) if e.to_string().contains(\"trailing bytes\") => {\n        log::warn(\"malformed env record dropped: {e}\");\n        // skip record, keep syncing\n    }\n    Err(e) => return Err(e.into()),\n}","preventionTips":["Always encode records with the library's own serialize, never by hand","Run all sync peers on the same Atuin version","Add a round-trip encode/decode test (proptest) for custom record writers"],"tags":["serialization","record-format","dotfiles"],"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"}