atuinsh/atuin · error
trailing bytes in encoded dotfiles env record, malformed
Error message
trailing bytes in encoded dotfiles env record, malformed
What it means
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.
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.
Example fix
// before: writing a record with an extra field let bytes = encode(name, value, export, extra_field); // after: encode exactly the fields the v0 schema defines let bytes = encode::to_bytes(&(name, value, export));
Defensive patterns
Strategy: validation
Validate before calling
// MsgPack-decode the payload first and check it has exactly 3 elements // before handing it to deserialize: let value: rmpv::Value = rmpv::decode::read_value(&mut cursor)?; assert_eq!(value.as_array().map(|a| a.len()), Some(3), "env record must have name, value, export");
Type guard
fn is_well_formed_env_record(payload: &[u8]) -> bool {
rmpv::decode::read_value(&mut std::io::Cursor::new(payload))
.ok()
.and_then(|v| v.as_array().map(|a| a.len() == 3))
.unwrap_or(false)
} Try / catch
match ShellEnv::deserialize(&bytes) {
Ok(env) => apply(env),
Err(e) if e.to_string().contains("trailing bytes") => {
log::warn("malformed env record dropped: {e}");
// skip record, keep syncing
}
Err(e) => return Err(e.into()),
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- too many entries in v0 dotfiles env create record, got
- too many entries in v0 dotfiles var delete record
- too many entries in v0 kv record
- too many entries in v0 script record
- too many entries in v0 shell alias create record
AI-assisted analysis of atuinsh/atuin@900007c480 (2026-09-19).
Data as JSON: /api/errors/9cdcddce087fa151.
Report an issue: GitHub.
Appendix: source
Thrown at crates/atuin-dotfiles/src/shell.rs:65
let nfields = decode::read_array_len(bytes).map_err(error_report)?;
ensure!(
nfields == 3,
"too many entries in v0 dotfiles env create record, got {}, expected {}",
nfields,
3
);
let bytes = bytes.remaining_slice();
let (key, bytes) = decode::read_str_from_slice(bytes).map_err(error_report)?;
let (value, bytes) = decode::read_str_from_slice(bytes).map_err(error_report)?;
let mut bytes = decode::Bytes::new(bytes);
let export = decode::read_bool(&mut bytes).map_err(error_report)?;
ensure!(
bytes.remaining_slice().is_empty(),
"trailing bytes in encoded dotfiles env record, malformed"
);
Ok(Self {
name: key.to_owned(),
value: value.to_owned(),
export,
})
}
}
#[must_use]
pub fn parse_alias(line: &str) -> Option<Alias> {
// consider the fact we might be importing a fish alias
// 'alias' output
// fish: alias foo bar
// posix: foo=barView on GitHub (pinned to 900007c480)