astrid-runtime/astrid · error

invalid receipt page number

Error message

invalid receipt page number: {error}

What it means

Thrown by page_number when the numeric portion of a receipt page filename fails to parse. The function strips the receipt suffix, splits on the page marker to isolate the number, then parses it; anything non-numeric is rejected. This guards the receipts directory against non-canonical or garbage filenames.

Solutions

  1. Rename or remove the offending file so the directory contains only canonical page names (uid + marker + numeric page + suffix).
  2. List the directory and confirm every file's trailing segment is a small non-negative integer; fix the one failing to parse.
  3. Do not manually manage receipt pages; let migration tooling create them.
  4. Exclude the receipts directory from editors/sync tools that create temp files.

Example fix

// before
receipt-<uid>-page-abc.json   // parse fails
// after
receipt-<uid>-page-0001.json
Defensive patterns

Strategy: validation

Validate before calling

fn page_name_is_canonical(name: &str, uid: &str) -> bool {
    name.strip_suffix(RECEIPT_SUFFIX)
        .and_then(|s| s.rsplit_once(RECEIPT_PAGE_MARKER))
        .map(|(u, n)| u == uid && n.parse::<u32>().is_ok())
        .unwrap_or(false)
}

Try / catch

match page_number(path) {
    Ok(n) => use(n),
    Err(e) if e.kind() == io::ErrorKind::InvalidData => {
        // non-canonical filename: quarantine/remove the file, then rescan
        let _ = fs::remove_file(path);
        rescan_receipts()?;
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: read_page resolves a filename where the text after the last RECEIPT_PAGE_MARKER (before RECEIPT_SUFFIX) is not a valid integer — letters, empty segment, or a value overflowing the target integer type.

Common situations: A user manually created or renamed page files; editors or sync tools left temp files like page-0001.json.crash partially matching; a filename from a different naming scheme/version is present.

Understand the failure class

Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.

Related errors


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

Appendix: source

Thrown at crates/astrid-kernel/src/principal_home_migration/receipts.rs:262

            Ok(page)
        },
    }
}

fn page_number(path: &Path) -> io::Result<u64> {
    let name = path
        .file_name()
        .and_then(|name| name.to_str())
        .ok_or_else(|| invalid_source(path, "receipt page name is not UTF-8"))?;
    let uid_and_page = name
        .strip_prefix(RECEIPT_PREFIX)
        .and_then(|name| name.strip_suffix(RECEIPT_SUFFIX))
        .ok_or_else(|| invalid_source(path, "receipt page name is not canonical"))?;
    let (_, number) = uid_and_page
        .rsplit_once(RECEIPT_PAGE_MARKER)
        .ok_or_else(|| invalid_source(path, "receipt page name is not canonical"))?;
    number.parse().map_err(|error| {
        io::Error::new(
            io::ErrorKind::InvalidData,
            format!("invalid receipt page number: {error}"),
        )
    })
}

pub(super) fn remove_stale_pages(home: &AstridHome, uid: PrincipalUid) -> io::Result<()> {
    let directory = home.migrations_dir();
    let prefix = format!("{RECEIPT_PREFIX}{uid}{RECEIPT_PAGE_MARKER}");
    for entry in fs::read_dir(directory)? {
        let entry = entry?;
        let name = entry.file_name();
        let Some(name) = name.to_str() else {
            continue;
        };
        if !name.starts_with(&prefix) || !name.ends_with(RECEIPT_SUFFIX) {
            continue;
        }

View on GitHub (pinned to affd8760f4)