EpicGames/lore · error · io::Error (UnexpectedEof)

level header file is

Error message

level header file is {} bytes, expected {expected}

What it means

`read_level_header_file` in lore-storage's fan-out reads a level marker file and expects at least `size_of::<LevelMarkerHeader>()` bytes. If the file is shorter, it returns `UnexpectedEof` with the actual vs expected byte count. (A missing file is tolerated and yields `Ok(None)` — only an existing-but-too-short file errors.) This indicates a truncated or corrupt marker file.

Solutions

  1. Delete the corrupt header file so the level is re-initialized (reads then return Ok(None)).
  2. Restore the file from backup or re-run the operation that was supposed to write the header.
  3. Check free disk space and whether previous writes crashed mid-write; fix the underlying cause before retrying.

Example fix

// before
let header = read_level_marker(dir).await?;

// after
match read_level_marker(dir).await {
    Ok(h) => h,
    Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => {
        fs::remove_file(header_path).ok(); // re-init level
        None
    }
    Err(e) => return Err(e),
}
Defensive patterns

Strategy: try-catch

Validate before calling

if let Ok(meta) = tokio::fs::metadata(&header_path).await {
    if meta.len() < std::mem::size_of::<LevelMarkerHeader>() as u64 {
        // header truncated: delete and let the level re-initialize
    }
}

Try / catch

match read_level_marker(dir).await {
    Ok(h) => h,
    Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => { remove_corrupt_header(dir)?; None },
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Reading a level marker/pending file (`read_level_marker`, `read_level_pending`) when the header file exists but contains fewer bytes than a zeroed `LevelMarkerHeader` — e.g. a partially written or truncated header.

Common situations: Crash or power loss between file creation and header write, disk-full during a header write, manual truncation of store files, or copy tools that truncated the file.

Understand the failure class

Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.

Related errors


AI-assisted analysis of EpicGames/lore@074eb0b0d1 (2026-09-13). Data as JSON: /api/errors/0cb7020e05c527b3. Report an issue: GitHub.

Appendix: source

Thrown at lore-storage/src/local/fan_out.rs:389

    {
        Ok(()) => Ok(()),
        Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(()),
        Err(err) => Err(err),
    }
}

/// Read a level-header file (marker or pending) from an explicit path. Shared format helper;
/// one backend dispatch covering open, read and close, the file being the header itself.
async fn read_level_header_file(path: &Path) -> std::io::Result<Option<usize>> {
    let bytes = match lore_io::IoDriver::global().read_file_bytes(path).await {
        Ok(bytes) => bytes,
        Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(None),
        Err(err) => return Err(err),
    };
    let mut header = LevelMarkerHeader::new_zeroed();
    let expected = size_of::<LevelMarkerHeader>();
    if bytes.len() < expected {
        return Err(std::io::Error::new(
            std::io::ErrorKind::UnexpectedEof,
            format!(
                "level header file is {} bytes, expected {expected}",
                bytes.len()
            ),
        ));
    }
    header.as_mut_bytes().copy_from_slice(&bytes[..expected]);
    if header.magic != MARKER_MAGIC {
        return Err(std::io::Error::new(
            std::io::ErrorKind::InvalidData,
            format!(
                "level header file has invalid magic 0x{:08x}, expected 0x{:08x}",
                header.magic, MARKER_MAGIC
            ),
        ));
    }
    if header.version != MARKER_VERSION {

View on GitHub (pinned to 074eb0b0d1)