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
- Delete the corrupt header file so the level is re-initialized (reads then return Ok(None)).
- Restore the file from backup or re-run the operation that was supposed to write the header.
- 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
- Write headers atomically (temp file + rename) so partial writes never appear.
- Watch for crashes/disk-full during store writes.
- Periodically validate header sizes in store maintenance code.
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
- anchor file ended before the anchor length
- level header file has invalid magic 0x
- Pager process stdin not available
- bytes exceeds the byte whole-file limit; open the file and…
- unsupported LORE_IO_BACKEND
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)