astrid-runtime/astrid · error

runtime tree receipt is not a regular file

Error message

runtime tree receipt is not a regular file: {}

What it means

The runtime tree receipt path exists but `symlink_metadata` shows it is not a regular file — e.g. a directory or a symlink. The kernel only accepts regular files as receipts, because receipts are read atomically and validated as private files. This is raised as InvalidData with the offending path in the message.

Solutions

  1. Remove the directory/symlink at the receipt path and let the app regenerate a real receipt file
  2. Re-run admission — an absent receipt is handled gracefully, so deleting the bogus path is safe
  3. Check with `ls -la` whether the path is a symlink and where it points
  4. Restore the receipt from backup as a plain regular file, not a link

Example fix

# before
$ ls -la receipt.json
receipt.json -> /shared/receipt.json
# after
$ rm receipt.json
# restart app; it recreates the receipt as a regular file
Defensive patterns

Strategy: validation

Validate before calling

let md = std::fs::symlink_metadata(&receipt_path)?;
if !md.is_file() {
    std::fs::remove_file(&receipt_path)?; // allow regeneration
}

Type guard

fn is_regular_file(p: &Path) -> bool {
    std::fs::symlink_metadata(p).map(|m| m.is_file()).unwrap_or(false)
}

Try / catch

if let Some(e) = err.downcast_ref::<io::Error>() {
    if e.kind() == io::ErrorKind::InvalidData && msg.contains("not a regular file") {
        std::fs::remove_file(&path)?; // then retry admission
    }
}

Prevention

When it happens

Trigger: `read_receipt` (called by `admit_blocking`) finds metadata where `!metadata.is_file()`: the receipt path is a directory, a symlink (to anywhere), a fifo, or another special file.

Common situations: A user or tool replaced the receipt with a symlink (e.g. linking it to shared state); a directory was created at the receipt path by mistake; restore tools recreated the path as a symlink.

Related errors


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

Appendix: source

Thrown at crates/astrid-kernel/src/runtime_tree_admit.rs:181

) -> io::Result<bool> {
    let receipt_bytes = fs::metadata(receipt_path)?.len();
    let catalog = store
        .content()
        .list(&StateOwner::System)
        .map_err(|error| io::Error::other(format!("list packed runtime catalog: {error}")))?;
    Ok(catalog.iter().any(|entry| {
        entry.name().as_str() == RECEIPT_RELATIVE_PATH && entry.logical_bytes() == receipt_bytes
    }))
}

fn read_receipt(path: &Path) -> io::Result<Option<RuntimeTreeReceipt>> {
    let metadata = match fs::symlink_metadata(path) {
        Ok(metadata) => metadata,
        Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(None),
        Err(error) => return Err(error),
    };
    if !metadata.is_file() {
        return Err(io::Error::new(
            io::ErrorKind::InvalidData,
            format!(
                "runtime tree receipt is not a regular file: {}",
                path.display()
            ),
        ));
    }
    if metadata.len() > MAX_RECEIPT_BYTES {
        return Err(io::Error::new(
            io::ErrorKind::InvalidData,
            format!("runtime tree receipt exceeds {MAX_RECEIPT_BYTES} bytes"),
        ));
    }
    astrid_core::platform_fs::validate_private_file(path)?;
    let bytes = fs::read(path)?;
    serde_json::from_slice(&bytes).map(Some).map_err(|error| {
        io::Error::new(
            io::ErrorKind::InvalidData,

View on GitHub (pinned to affd8760f4)