EpicGames/lore · error · io::Error (UnexpectedEof)
anchor file ended before the anchor length
Error message
anchor file ended before the anchor length
What it means
When migrating an old-format anchor file, lore-revision reads the file and needs exactly `size_of::<OldAnchor>()` bytes to reconstruct the fixed-layout struct via `as_mut_bytes`. If the file on disk is shorter than that fixed size, deserialization cannot proceed and the function returns `UnexpectedEof`. This guards against truncated, corrupted, or wrong-format files being treated as valid anchors.
Solutions
- Restore the anchor file from backup or re-fetch it, since its content is unrecoverable in place.
- Verify the file size is at least `size_of::<OldAnchor>()` before running the migration.
- Delete/recreate the anchor via the library's normal write path if the data is regenerable.
Example fix
// before
let bytes = read(path).await?;
let data = bytes.get(..size_of::<OldAnchor>()).ok_or(...)?;
// after
let bytes = read(path).await?;
let need = size_of::<OldAnchor>();
if bytes.len() < need {
return Err(io::Error::new(io::ErrorKind::UnexpectedEof,
format!("anchor file too short: {} < {need}", bytes.len())));
}
let data = &bytes[..need]; Defensive patterns
Strategy: try-catch
Validate before calling
let meta = tokio::fs::metadata(&anchor_path).await?;
if meta.len() < std::mem::size_of::<OldAnchor>() as u64 {
// restore from backup before migrating
} Try / catch
match migrate_old(&path).await {
Ok(v) => v,
Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => {
eprintln!("anchor file truncated: {}", e); restore_from_backup()?;
}
Err(e) => return Err(e),
} Prevention
- Back up anchor files before running migrations.
- Ensure writes of the anchor are atomic (temp file + rename).
- Monitor disks for full-disk/partial-write conditions that truncate files.
When it happens
Trigger: Calling the migrate-old deserialization path on an anchor file that is truncated (partial write/copy), corrupted by disk issues, or written by a format whose serialized size is smaller than `OldAnchor`.
Common situations: Interrupted writes (crash/power loss during save), rsync/backup tools that truncated files, repository files hand-edited or damaged, or an old store created by a pre-`OldAnchor` layout.
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
- level header file is
- Pager process stdin not available
- bytes exceeds the byte whole-file limit; open the file and…
- unsupported LORE_IO_BACKEND
- file ended before the requested read length
AI-assisted analysis of EpicGames/lore@074eb0b0d1 (2026-09-13).
Data as JSON: /api/errors/6430a116c2b81f5d.
Report an issue: GitHub.
Appendix: source
Thrown at lore-revision/src/anchor.rs:58
/// Read an old file-based anchor (48 bytes: 32-byte hash + 16-byte branch ID)
/// for migration purposes only.
pub async fn deserialize_migrate_old(
path: impl AsRef<Path> + Send,
) -> std::io::Result<(Hash, BranchId)> {
let path = path.as_ref().to_path_buf();
#[repr(C)]
#[derive(Default, IntoBytes, FromBytes, Immutable)]
struct OldAnchor {
signature: Hash,
branch: BranchId,
}
let bytes = lore_io::IoDriver::global().read_file_bytes(&path).await?;
let mut anchor = OldAnchor::default();
let length = anchor.as_bytes().len();
let Some(data) = bytes.get(..length) else {
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"anchor file ended before the anchor length",
));
};
anchor.as_mut_bytes().copy_from_slice(data);
Ok((anchor.signature, anchor.branch))
}
View on GitHub (pinned to 074eb0b0d1)