{"record":{"id":"91294e6dab39d82d","repo":"stalwartlabs/stalwart","slug":"failed-to-deserialize-counter-quota","errorCode":null,"errorMessage":"Failed to deserialize counter/quota","messagePattern":"Failed to deserialize counter/quota","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/common/src/manager/restore.rs","lineNumber":134,"sourceCode":"        SUBSPACE_BLOBS => {\n            while let Some((key, value)) = reader.next() {\n                blob_store\n                    .put_blob(&key, &value, CompressionAlgo::Lz4)\n                    .await\n                    .failed(\"Failed to write blob\");\n            }\n        }\n        SUBSPACE_COUNTER | SUBSPACE_QUOTA => {\n            while let Some((key, value)) = reader.next() {\n                batch.add(\n                    ValueClass::Any(AnyClass {\n                        subspace: reader.subspace,\n                        key,\n                    }),\n                    u64::from_le_bytes(\n                        value\n                            .try_into()\n                            .expect(\"Failed to deserialize counter/quota\"),\n                    ) as i64,\n                );\n                if batch.is_large_batch() {\n                    store\n                        .write(batch.build_all())\n                        .await\n                        .failed(\"Failed to write batch\");\n                    batch = BatchBuilder::new();\n                }\n            }\n        }\n        SUBSPACE_INDEXES => {\n            while let Some((key, _)) = reader.next() {\n                let account_id = key\n                    .as_slice()\n                    .deserialize_be_u32(0)\n                    .failed(\"Failed to deserialize account ID\");\n                let collection = *key.get(U32_LEN).failed(\"Missing collection byte\");","sourceCodeStart":116,"sourceCodeEnd":152,"githubUrl":"https://github.com/stalwartlabs/stalwart/blob/e96200385781a6a9995a8b839ac27d6c75a983ee/crates/common/src/manager/restore.rs#L116-L152","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":"// before\nu64::from_le_bytes(value.try_into().expect(\"Failed to deserialize counter/quota\")) as i64\n// after\nlet bytes: &[u8; 8] = value.try_into().unwrap_or(&[0u8; 8]);\nu64::from_le_bytes(*bytes) as i64","handlingStrategy":"validation","validationCode":"fn valid_counter(v: &[u8]) -> bool { v.len() == 8 }\n// skip or log entries where !valid_counter(value) before try_into","typeGuard":"fn as_counter_bytes(v: &[u8]) -> Option<[u8; 8]> { v.try_into().ok() }","tryCatchPattern":"// panics are not catchable in normal Rust flow; sanitize at source:\nmatch <[u8; 8]>::try_from(value) {\n    Ok(b) => u64::from_le_bytes(b) as i64,\n    Err(_) => { log::warn!(\"skipping malformed counter entry\"); continue; }\n}","preventionTips":["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"],"tags":["database","restore","deserialization","corruption"],"backgroundTag":"schema-validation-failed","analyzedSha":"e96200385781a6a9995a8b839ac27d6c75a983ee","analyzedAt":"2026-09-06T22:07:17.982Z","contentChangedAt":"2026-09-06T22:07:17.982Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}