astrid-runtime/astrid · error
principal-home migration receipt exceeds
Error message
principal-home migration receipt exceeds {MAX_RECEIPT_INDEX_BYTES} bytes What it means
`read_receipt` loads a principal-home migration receipt index and enforces a hard cap of MAX_RECEIPT_INDEX_BYTES on the file size before JSON parsing. This bounds memory and rejects oversized (possibly tampered or foreign) files. When the receipt file on disk exceeds the cap, the read fails with this InvalidData io::Error instead of attempting to parse it.
Solutions
- Inspect the receipt file at the path; if it is corrupted or contains concatenated/foreign data, remove it and re-run the migration so a fresh canonical receipt is written.
- Verify the receipt is canonical JSON of a MigrationReceipt within the size cap; rebuild it with write_receipt if it was produced by an older/larger format.
- Do not simply raise MAX_RECEIPT_INDEX_BYTES unless you also control all readers — it is a DoS/tamper bound; if raised, do so consistently across versions.
- Check for processes writing to the migrations directory (backups, log redirections) and exclude it from such tools.
Example fix
// before cat receipt-one.json receipt-two.json > .astrid-migrations/receipt-<uid>.json // oversized concatenated receipt // after let receipt = write_receipt(path, ¤t_receipt)?; // single canonical receipt within the byte cap
Defensive patterns
Strategy: try-catch
Validate before calling
// check size before asking the library to parse the receipt
fn receipt_is_plausible(path: &Path) -> io::Result<bool> {
Ok(fs::metadata(path)?.len() <= MAX_RECEIPT_INDEX_BYTES as u64)
} Try / catch
match read_receipt(&receipt_path) {
Err(e) if e.kind() == io::ErrorKind::InvalidData && e.to_string().contains("receipt exceeds") => {
eprintln!("receipt file oversized/corrupted, remove and re-run migration: {e}");
},
Err(e) => return Err(e),
Ok(None) => eprintln!("no prior receipt"),
Ok(Some(receipt)) => { /* proceed */ },
} Prevention
- Never write non-receipt data (logs, backups, concatenated files) to the receipt path in the migrations directory.
- Exclude the migrations directory from backup tools that merge or append files.
- Write receipts only via write_receipt so size and canonicity are enforced at write time.
- Keep MAX_RECEIPT_INDEX_BYTES consistent across all deployed library versions reading the same data.
When it happens
Trigger: Any caller (`migrate_one_principal`, `retire_one_receipted_source`, `verify_migrated_legacy_principal_sources_retired`) invoking `read_receipt` on a receipt file whose byte length exceeds MAX_RECEIPT_INDEX_BYTES — e.g. a corrupted, concatenated, or hand-edited receipt, or a file accidentally written to the receipt path that is not a real receipt.
Common situations: A backup/restore tool appending multiple receipts into one file; logs or crash dumps written over the receipt path; an old writer producing larger receipts than the current MAX_RECEIPT_INDEX_BYTES allows; deliberate tampering attempts (the cap is a security bound).
Understand the failure class
Background: "File too large" / "file size exceeds limit" errors: why libraries cap file sizes and how to fix them — this error's family across 46 libraries.
Related errors
- principal-home migration page exceeds
- absent migration source has a digest
- decode migration ledger
- durable capsule disappeared after publish
- durable capsule failed authoritative verification
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/f9ce0ea16e040777.
Report an issue: GitHub.
Appendix: source
Thrown at crates/astrid-kernel/src/principal_home_migration/receipts.rs:163
&receipt_path(home, uid),
"receipt entry count does not match its pages",
));
}
Ok(())
}
pub(super) fn read_receipt(path: &Path) -> io::Result<Option<MigrationReceipt>> {
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"));
}View on GitHub (pinned to affd8760f4)