astrid-runtime/astrid · error
decode legacy log receipt
Error message
decode legacy log receipt {}: {error} What it means
The legacy log receipt file could not be parsed as JSON into a `LogReceipt`, or (subsequently) its canonical JSON re-encoding does not byte-match the stored bytes. The kernel treats receipts as strictly canonical JSON, so any whitespace/ordering difference or corrupt content is rejected with InvalidData and the path plus serde error in the message.
Solutions
- Delete the corrupt receipt file and re-run the migration so it is rewritten
- Check the serde error in the message to see whether it is truncation vs. wrong structure
- Restore the receipt from a known-good backup
- Never reformat receipt JSON by hand — the kernel requires byte-exact canonical form
Defensive patterns
Strategy: try-catch
Try / catch
match decode_receipt(&bytes, &path) {
Ok(r) => use_receipt(r),
Err(e) if e.kind() == io::ErrorKind::InvalidData => {
// corrupt/non-canonical receipt: regenerate
std::fs::remove_file(&path)?;
re_run_migration()?;
}
Err(e) => return Err(e),
} Prevention
- Don't open receipts in editors that reformat JSON — canonical bytes are required
- Shut down cleanly; crashes mid-write can truncate receipts
- Keep backups of the home before migrating
When it happens
Trigger: `decode_receipt` is called by `legacy_log_destination_proof` and `migrate_one` on bytes read from the receipt file; serde_json::from_slice fails (truncated file, binary garbage, partial write) or `canonical_json(&receipt) != bytes`.
Common situations: Crash or power loss mid-write leaving a truncated receipt; the file was edited with an editor that reformatted JSON; disk corruption; an incompatible receipt format from a very old version.
Related errors
- parse runtime tree receipt
- byte value must be non-negative and finite
- decode migration ledger
- empty byte specifier
- empty duration
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/e57745f3ed9ab087.
Report an issue: GitHub.
Appendix: source
Thrown at crates/astrid-kernel/src/principal_log_migration.rs:443
if metadata.file_type().is_symlink() || !metadata.is_file() {
return Err(invalid(path, "legacy log receipt is not a regular file"));
}
astrid_core::platform_fs::verify_no_redirects(path)?;
astrid_core::platform_fs::validate_private_file(path)?;
if metadata.len() > MAX_RECEIPT_BYTES {
return Err(invalid(path, "legacy log receipt exceeds size limit"));
}
}
match fs::read(path) {
Ok(bytes) => Ok(Some(bytes)),
Err(error) if error.kind() == io::ErrorKind::NotFound => Ok(None),
Err(error) => Err(error),
}
}
fn decode_receipt(bytes: &[u8], path: &Path) -> io::Result<LogReceipt> {
let receipt: LogReceipt = serde_json::from_slice(bytes).map_err(|error| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("decode legacy log receipt {}: {error}", path.display()),
)
})?;
if canonical_json(&receipt)? != bytes {
return Err(invalid(path, "legacy log receipt is not canonical JSON"));
}
Ok(receipt)
}
fn canonical_json<T: Serialize>(value: &T) -> io::Result<Vec<u8>> {
serde_json::to_vec(value).map_err(io::Error::other)
}
fn digest_file(path: &Path, expected_device: u64) -> io::Result<(u64, String)> {
let metadata = fs::symlink_metadata(path)?;
if metadata.file_type().is_symlink()
|| !metadata.is_file()View on GitHub (pinned to affd8760f4)