{"record":{"id":"6430a116c2b81f5d","repo":"EpicGames/lore","slug":"anchor-file-ended-before-the-anchor-length","errorCode":null,"errorMessage":"anchor file ended before the anchor length","messagePattern":"anchor file ended before the anchor length","errorType":"exception","errorClass":"io::Error (UnexpectedEof)","httpStatus":null,"severity":"error","filePath":"lore-revision/src/anchor.rs","lineNumber":58,"sourceCode":"/// Read an old file-based anchor (48 bytes: 32-byte hash + 16-byte branch ID)\n/// for migration purposes only.\npub async fn deserialize_migrate_old(\n    path: impl AsRef<Path> + Send,\n) -> std::io::Result<(Hash, BranchId)> {\n    let path = path.as_ref().to_path_buf();\n\n    #[repr(C)]\n    #[derive(Default, IntoBytes, FromBytes, Immutable)]\n    struct OldAnchor {\n        signature: Hash,\n        branch: BranchId,\n    }\n\n    let bytes = lore_io::IoDriver::global().read_file_bytes(&path).await?;\n    let mut anchor = OldAnchor::default();\n    let length = anchor.as_bytes().len();\n    let Some(data) = bytes.get(..length) else {\n        return Err(io::Error::new(\n            io::ErrorKind::UnexpectedEof,\n            \"anchor file ended before the anchor length\",\n        ));\n    };\n    anchor.as_mut_bytes().copy_from_slice(data);\n    Ok((anchor.signature, anchor.branch))\n}\n","sourceCodeStart":40,"sourceCodeEnd":66,"githubUrl":"https://github.com/EpicGames/lore/blob/074eb0b0d1194c997d7cf28b55519e3e197b3e23/lore-revision/src/anchor.rs#L40-L66","documentation":"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.","triggerScenarios":"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`.","commonSituations":"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.","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."],"exampleFix":"// before\nlet bytes = read(path).await?;\nlet data = bytes.get(..size_of::<OldAnchor>()).ok_or(...)?;\n\n// after\nlet bytes = read(path).await?;\nlet need = size_of::<OldAnchor>();\nif bytes.len() < need {\n    return Err(io::Error::new(io::ErrorKind::UnexpectedEof,\n        format!(\"anchor file too short: {} < {need}\", bytes.len())));\n}\nlet data = &bytes[..need];","handlingStrategy":"try-catch","validationCode":"let meta = tokio::fs::metadata(&anchor_path).await?;\nif meta.len() < std::mem::size_of::<OldAnchor>() as u64 {\n    // restore from backup before migrating\n}","typeGuard":null,"tryCatchPattern":"match migrate_old(&path).await {\n    Ok(v) => v,\n    Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => {\n        eprintln!(\"anchor file truncated: {}\", e); restore_from_backup()?;\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["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."],"tags":["io","deserialization","truncated-file"],"backgroundTag":"file-read-failed","analyzedSha":"074eb0b0d1194c997d7cf28b55519e3e197b3e23","analyzedAt":"2026-09-13T09:00:57.509Z","contentChangedAt":"2026-09-13T09:00:57.509Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}