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

  1. 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>`).
  2. 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.
  3. Check for an Atuin version mismatch between sync peers and upgrade all clients to the same version.
  4. 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

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.

Related errors


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=bar

View on GitHub (pinned to 900007c480)