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

  1. Restore the anchor file from backup or re-fetch it, since its content is unrecoverable in place.
  2. Verify the file size is at least `size_of::<OldAnchor>()` before running the migration.
  3. 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

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


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)