atuinsh/atuin · error
unknown HistoryRecord type
Error message
unknown HistoryRecord type {n} What it means
`HistoryRecord::deserialize` only knows record-type bytes 0 (Create) and 1 (Delete). Any other leading type byte `n` is rejected with "unknown HistoryRecord type {n}", guarding against records from newer or incompatible protocol versions.
Solutions
- Upgrade atuin to the latest version on the machine showing the error so it understands newer record types
- Ensure all devices in the sync network run compatible atuin versions
- Check whether the record store was written by a newer version (avoid downgrading across record-format changes)
- If corruption is suspected, re-sync the affected records from the server
Defensive patterns
Strategy: try-catch
Try / catch
match record_decode_result {
Err(e) if e.to_string().contains("unknown HistoryRecord type") => {
eprintln!("Record from newer atuin version; upgrade CLI before syncing");
}
other => other?,
} Prevention
- Avoid running older atuin binaries against record stores written by newer versions
- Upgrade all machines in the sync network together
- Pin atuin versions in managed environments
When it happens
Trigger: Deserializing a history record whose first byte is not 0 or 1 — typically a record type added in a newer atuin version being read by an older client, or a corrupted/truncated payload shifting byte alignment.
Common situations: Downgraded atuin CLI reading a record store written by a newer version; syncing with a peer on a different atuin version; binary corruption in the local record DB.
Related errors
- trailing bytes decoding HistoryRecord::Delete - malformed?…
- trailing bytes in encoded shell alias record. malformed
- could not sync records due to version mismatch
- invalid id UUID format in sqlite DB
- the encryption key on this machine does not match the data…
AI-assisted analysis of atuinsh/atuin@900007c480 (2026-09-19).
Data as JSON: /api/errors/5286c47105b6c596.
Report an issue: GitHub.
Appendix: source
Thrown at crates/atuin-client/src/history/store.rs:121
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();
/// Records appended to the store per transaction by [`HistoryStore::push_records`]. Each chunk is
/// encrypted before its write lock is taken, so this bounds both how long a concurrent writer can
/// wait on the lock and how much work a lost race throws away.
const APPEND_BATCH_SIZE: usize = 1000;
/// How many records `incremental_build` decodes concurrently. Decoding is read-then-decrypt per
/// record; overlapping the reads keeps the record store's pool busy without unbounded fan-out.
/// Kept under the store's connection pool size so decodes don't starve other readers.
const DECODE_CONCURRENCY: usize = 4;View on GitHub (pinned to 900007c480)