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

  1. Upgrade atuin to the latest version on the machine showing the error so it understands newer record types
  2. Ensure all devices in the sync network run compatible atuin versions
  3. Check whether the record store was written by a newer version (avoid downgrading across record-format changes)
  4. 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

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


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)