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

  1. Run jq (or similar) against the page file to read the serde error and correct or delete the malformed page.
  2. Delete the invalid page and re-run the migration so a fresh page is written.
  3. Ensure the reading binary and the writing binary agree on the MigrationPage schema.
  4. 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

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


AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/ad6a751697852baf. Report an issue: GitHub.