atuinsh/atuin · error

trailing bytes decoding HistoryRecord::Delete - malformed?…

Error message

trailing bytes decoding HistoryRecord::Delete - malformed? got {bytes:?}

What it means

`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.

Solutions

  1. Identify the offending record and delete it from the record store so sync can re-fetch a clean copy
  2. Back up and inspect the record DB (`~/.local/share/atuin/records.db` or similar) for corruption
  3. Update atuin on all syncing machines to matching versions to eliminate format skew
  4. As a last resort, reset the record store and re-sync from the server
Defensive patterns

Strategy: try-catch

Try / catch

match record_decode_result {
    Err(e) if e.to_string().contains("trailing bytes decoding HistoryRecord::Delete") => {
        // quarantine the record id and re-sync it
    }
    other => other?,
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "cannot parse invalid wire-format data", "cannot unmarshal", "failed unmarshalling": protobuf unmarshal errors explained — this error's family across 10 libraries.

Related errors


AI-assisted analysis of atuinsh/atuin@900007c480 (2026-09-19). Data as JSON: /api/errors/1813dfdca0291792. Report an issue: GitHub.

Appendix: source

Thrown at crates/atuin-client/src/history/store.rs:112

            // 0 -> HistoryRecord::Create
            0 => {
                // not super useful to us atm, but perhaps in the future
                // written by write_bin above
                let _ = decode::read_bin_len(&mut bytes).map_err(error_report)?;

                let record =
                    History::deserialize(bytes.remaining_slice(), version).map_err(error_report)?;

                Ok(Self::Create(record))
            }

            // 1 -> HistoryRecord::Delete
            1 => {
                let bytes = bytes.remaining_slice();
                let (id, bytes) = decode::read_str_from_slice(bytes).map_err(error_report)?;

                if !bytes.is_empty() {
                    bail!(
                        "trailing bytes decoding HistoryRecord::Delete - malformed? got {bytes:?}"
                    );
                }

                Ok(Self::Delete(id.parse()?))
            }

            n => {
                bail!("unknown HistoryRecord type {n}");
            }
        }
    }
}

/// How many entries `incremental_build` holds in memory, and the most it puts into a single
/// `save_bulk`/`delete_rows` transaction.
const BUILD_BATCH_SIZE: NonZeroUsize = NonZeroUsize::new(5000).unwrap();

View on GitHub (pinned to 900007c480)