atuinsh/atuin · error

trailing bytes decoding HistoryRecord::Delete - malformed? g

Error message

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

What it means

While decoding a HistoryRecord tagged as Delete (tag 1), the payload contained bytes left over after reading the record ID. The record store treats this as corruption since Delete records must contain exactly one string field.

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 c0c717ab04)

Solutions

  1. Verify the record store integrity; re-sync from the Hub by deleting the local record DB and re-downloading
  2. Update atuin to the latest version on all machines to align record formats
  3. Restore the record store from backup if corruption persists
Defensive patterns

Strategy: try-catch

Type guard

fn is_wellformed_delete(payload: &[u8]) -> bool {
    match decode::read_str_from_slice(payload) {
    	Ok((_, rest)) => rest.is_empty(),
    	Err(_) => false,
    }
}

Try / catch

match HistoryRecord::deserialize(bytes) {
    Ok(rec) => apply(rec),
    Err(e) if e.to_string().contains("trailing bytes") => {
        log::warn!("corrupt record skipped: {e}");
        // skip or trigger re-sync
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Deserializing a binary record whose Delete payload has extra trailing bytes — truncated/corrupted record store files, or records written by an incompatible format version.

Common situations: Partially written or damaged SQLite record blobs; syncing records from a newer/older atuin version with format drift.

Understand the failure class

Related errors


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