astrid-runtime/astrid · error
invalid principal-home migration receipt: {error}
Error message
invalid principal-home migration receipt: {error} What it means
Thrown when a serialized principal-home migration receipt file fails to deserialize into a MigrationReceipt via serde_json. The library wraps the serde error in an io::Error with InvalidData kind because migration state files must be valid, schema-conformant JSON to be trusted. A corrupt or tampered receipt is treated as unusable migration state rather than being partially parsed.
Source
Thrown at crates/astrid-kernel/src/principal_home_migration/receipts.rs:171
match fs::symlink_metadata(path) {
Err(error) if error.kind() == io::ErrorKind::NotFound => Ok(None),
Err(error) => Err(error),
Ok(metadata) if metadata.file_type().is_symlink() || !metadata.is_file() => {
Err(invalid_source(path, "receipt is not a regular file"))
},
Ok(_) => {
astrid_core::platform_fs::validate_private_file(path)?;
let bytes = fs::read(path)?;
if bytes.len() > MAX_RECEIPT_INDEX_BYTES {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"principal-home migration receipt exceeds {MAX_RECEIPT_INDEX_BYTES} bytes"
),
));
}
let receipt: MigrationReceipt = serde_json::from_slice(&bytes).map_err(|error| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("invalid principal-home migration receipt: {error}"),
)
})?;
if receipt.page_count.get() > receipt.entry_count.get()
|| (receipt.entry_count == EntryCount::ZERO
&& receipt.page_count != PageCount::ZERO)
{
return Err(invalid_source(path, "receipt page count is not canonical"));
}
let canonical = canonical_json(&receipt)?;
if bytes != canonical {
return Err(invalid_source(path, "receipt is not canonical JSON"));
}
Ok(Some(receipt))
},
}
}View on GitHub (pinned to affd8760f4)
Solutions
- Inspect the receipt file at the failing path with a JSON validator (jq) to read the underlying serde error and fix or remove the malformed file.
- Delete the invalid receipt so migration re-runs and writes a fresh, valid receipt.
- If caused by a version upgrade, align the running binary with the schema version that wrote the receipt, or re-run migration from pre-migration source state.
- Ensure writes go through write_receipt (atomic, canonical JSON) rather than hand-editing receipt files.
Example fix
// before
let raw = std::fs::read_to_string("receipt.json")?;
// after
let bytes = std::fs::read("receipt.json")?;
let receipt: MigrationReceipt = serde_json::from_slice(&bytes)?;
write_receipt(path, &receipt)?; // canonical, size-checked, atomic Defensive patterns
Strategy: validation
Validate before calling
fn receipt_is_parseable(bytes: &[u8]) -> bool {
serde_json::from_slice::<serde_json::Value>(bytes).is_ok()
&& serde_json::from_slice::<MigrationReceipt>(bytes).is_ok()
} Try / catch
match read_receipt(path) {
Ok(r) => use(r),
Err(e) if e.kind() == io::ErrorKind::InvalidData => {
// quarantine/delete the corrupt receipt and re-run migration
let _ = fs::remove_file(path);
reinitialize_migration()?;
}
Err(e) => return Err(e),
} Prevention
- Never hand-edit receipt files; only use write_receipt.
- Keep all homes on the same binary/schema version during migration.
- Do not kill the process mid-atomic-write; rely on the library's atomic file writes.
- Back up the legacy home before migration so corrupt state can be restored.
When it happens
Trigger: read_receipt is called (directly or via verify_migrated_legacy_principal_sources_retired, retire_one_receipted_source, or migrate_one_principal) and the receipt file contains malformed JSON, wrong field types, missing required fields, or values that violate MigrationReceipt's serde constraints.
Common situations: Disk corruption or a truncated write from a previous crash; manual editing of migration state files; a schema change in MigrationReceipt (renamed/removed fields) leaving stale receipts from an older version; copying receipt files between homes with different code versions.
Understand the failure class
Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.
Related errors
- invalid principal-home migration page: {error}
- WouldBlock
- legacy path {text:?} is not a canonical filesystem path: {er
- legacy principal-home source {}: {detail}
- principal-home migration conflict at {}: {detail}
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/00df14d4c379a971.
Report an issue: GitHub.