stalwartlabs/stalwart · critical

Failed to deserialize counter/quota

Error message

Failed to deserialize counter/quota

What it means

This panic occurs during RocksDB restore when a stored counter or quota value cannot be converted to a 8-byte little-endian u64 via `try_into()`. It means the database entry referenced as a counter/quota has a byte length other than 8, indicating a corrupt or foreign key-value entry in the backup being restored. The `expect` converts the failed slice-to-array conversion into a hard panic, aborting the restore.

Source

Thrown at crates/common/src/manager/restore.rs:134

        SUBSPACE_BLOBS => {
            while let Some((key, value)) = reader.next() {
                blob_store
                    .put_blob(&key, &value, CompressionAlgo::Lz4)
                    .await
                    .failed("Failed to write blob");
            }
        }
        SUBSPACE_COUNTER | SUBSPACE_QUOTA => {
            while let Some((key, value)) = reader.next() {
                batch.add(
                    ValueClass::Any(AnyClass {
                        subspace: reader.subspace,
                        key,
                    }),
                    u64::from_le_bytes(
                        value
                            .try_into()
                            .expect("Failed to deserialize counter/quota"),
                    ) as i64,
                );
                if batch.is_large_batch() {
                    store
                        .write(batch.build_all())
                        .await
                        .failed("Failed to write batch");
                    batch = BatchBuilder::new();
                }
            }
        }
        SUBSPACE_INDEXES => {
            while let Some((key, _)) = reader.next() {
                let account_id = key
                    .as_slice()
                    .deserialize_be_u32(0)
                    .failed("Failed to deserialize account ID");
                let collection = *key.get(U32_LEN).failed("Missing collection byte");

View on GitHub (pinned to e962003857)

Solutions

  1. Verify the backup is complete and was taken with a compatible server version; re-take the backup and retry the restore
  2. Check for corruption in the source data files and restore from a known-good backup
  3. Only copy database files while the server is stopped, or use the built-in backup/export tooling
  4. Inspect the offending key's value length to confirm the schema mismatch before restoring

Example fix

// before
u64::from_le_bytes(value.try_into().expect("Failed to deserialize counter/quota")) as i64
// after
let bytes: &[u8; 8] = value.try_into().unwrap_or(&[0u8; 8]);
u64::from_le_bytes(*bytes) as i64
Defensive patterns

Strategy: validation

Validate before calling

fn valid_counter(v: &[u8]) -> bool { v.len() == 8 }
// skip or log entries where !valid_counter(value) before try_into

Type guard

fn as_counter_bytes(v: &[u8]) -> Option<[u8; 8]> { v.try_into().ok() }

Try / catch

// panics are not catchable in normal Rust flow; sanitize at source:
match <[u8; 8]>::try_from(value) {
    Ok(b) => u64::from_le_bytes(b) as i64,
    Err(_) => { log::warn!("skipping malformed counter entry"); continue; }
}

Prevention

When it happens

Trigger: Restoring a database whose counter/quota records contain values that are not exactly 8 bytes (e.g. a backup from a different schema version, a truncated/corrupted store file, or hand-edited/copied data files).

Common situations: Restoring a backup taken with an older Stalwart version whose on-disk counter encoding differs; restoring from a partially copied or corrupted data directory; manually copying RocksDB files while the server was running.

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 stalwartlabs/stalwart@e962003857 (2026-09-06). Data as JSON: /api/errors/91294e6dab39d82d. Report an issue: GitHub.