astrid-runtime/astrid · error
invalid principal-home migration page: {error}
Error message
invalid principal-home migration page: {error} What it means
Thrown by read_page when a receipt page's bytes do not deserialize into a MigrationPage via serde_json. Parsing itself failed; subsequent checks (schema must be ReceiptSchema::V2, uid must match) only run after successful parse. The library refuses to interpret malformed page data as migration state.
Source
Thrown at crates/astrid-kernel/src/principal_home_migration/receipts.rs:223
page: u64,
) -> io::Result<MigrationPage> {
let path = page_path(home, uid, page);
match fs::symlink_metadata(&path) {
Err(error) => Err(error),
Ok(metadata) if metadata.file_type().is_symlink() || !metadata.is_file() => {
Err(invalid_source(&path, "receipt page is not a regular file"))
},
Ok(_) => {
astrid_core::platform_fs::validate_private_file(&path)?;
let bytes = fs::read(&path)?;
if bytes.len() > MAX_RECEIPT_PAGE_BYTES {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("principal-home migration page exceeds {MAX_RECEIPT_PAGE_BYTES} bytes"),
));
}
let page: MigrationPage = serde_json::from_slice(&bytes).map_err(|error| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("invalid principal-home migration page: {error}"),
)
})?;
if page.schema != ReceiptSchema::V2
|| page.uid != uid
|| page.page != page_number(&path)?
{
return Err(invalid_source(
&path,
"receipt page identity is not canonical",
));
}
if page.entries.len() > PAGE_ENTRY_LIMIT {
return Err(invalid_source(&path, "receipt page has too many entries"));
}
let canonical = canonical_json(&page)?;
if bytes != canonical {View on GitHub (pinned to affd8760f4)
Solutions
- Run jq (or similar) against the page file to read the serde error and correct or delete the malformed page.
- Delete the invalid page and re-run the migration so a fresh page is written.
- Ensure the reading binary and the writing binary agree on the MigrationPage schema.
- Only create/modify pages via the library's write paths (canonical JSON, atomic write).
Defensive patterns
Strategy: try-catch
Validate before calling
fn page_is_parseable(bytes: &[u8]) -> bool {
serde_json::from_slice::<MigrationPage>(bytes).is_ok()
} Try / catch
match read_page(path, uid) {
Ok(p) => use(p),
Err(e) if e.kind() == io::ErrorKind::InvalidData => {
let _ = fs::remove_file(path); // discard malformed page
re_run_migration_for(uid)?;
}
Err(e) => return Err(e),
} Prevention
- Only write pages via the library's canonical-JSON atomic writer.
- Keep writer and reader binaries on the same schema version.
- Back up pages before schema upgrades.
- Never hand-edit page JSON.
When it happens
Trigger: read_page (via retire_one_receipted_source, verify_destinations, validate_receipt_pages) parses a page file containing invalid JSON, wrong field types, or fields missing from MigrationPage.
Common situations: Truncated page write after a crash or power loss; manual edits to page files; pages written by an incompatible schema version; copying pages between different homes or 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 receipt: {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/ad6a751697852baf.
Report an issue: GitHub.