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

  1. Delete the corrupt receipt file and re-run the migration so it is rewritten
  2. Check the serde error in the message to see whether it is truncation vs. wrong structure
  3. Restore the receipt from a known-good backup
  4. 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

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


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)