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
- Verify the backup is complete and was taken with a compatible server version; re-take the backup and retry the restore
- Check for corruption in the source data files and restore from a known-good backup
- Only copy database files while the server is stopped, or use the built-in backup/export tooling
- 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
- Always stop the server before copying database files
- Take backups with the built-in export/backup tool rather than raw file copies
- Verify backup compatibility with the target server version before restoring
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
- Unknown database schema version, expected {} or below, found
- Migration aborted: {message}
- Failed to scan keys
- Failed to delete keys
AI-assisted analysis of stalwartlabs/stalwart@e962003857 (2026-09-06).
Data as JSON: /api/errors/91294e6dab39d82d.
Report an issue: GitHub.