{"record":{"id":"db28e29eccd7c038","repo":"astrid-runtime/astrid","slug":"positional-read-reached-end-of-file","errorCode":null,"errorMessage":"positional read reached end of file","messagePattern":"positional read reached end of file","errorType":"exception","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"crates/astrid-storage/src/engine/durable/format/mod.rs","lineNumber":207,"sourceCode":"        .try_into()\n        .map_err(|_| DurableError::EncodingOverflow)?;\n    if frame_checksum(magic, payload_len, &payload) != checksum {\n        return Err(corrupt(file_name, offset, \"frame checksum mismatch\"));\n    }\n    Ok(payload)\n}\n\nfn read_exact_at<F: DurableIo>(file: &F, buffer: &mut [u8], offset: u64) -> io::Result<()> {\n    let mut filled = 0_usize;\n    while filled != buffer.len() {\n        let relative = u64::try_from(filled)\n            .map_err(|_| io::Error::other(\"positional read offset overflow\"))?;\n        let position = offset\n            .checked_add(relative)\n            .ok_or_else(|| io::Error::other(\"positional read offset overflow\"))?;\n        let read = positioned_read(file, &mut buffer[filled..], position)?;\n        if read == 0 {\n            return Err(io::Error::new(\n                io::ErrorKind::UnexpectedEof,\n                \"positional read reached end of file\",\n            ));\n        }\n        filled = filled\n            .checked_add(read)\n            .ok_or_else(|| io::Error::other(\"positional read length overflow\"))?;\n    }\n    Ok(())\n}\n\nfn positioned_read<F: DurableIo>(file: &F, buffer: &mut [u8], offset: u64) -> io::Result<usize> {\n    file.durable_read_at(buffer, offset)\n}\n\n#[cfg(test)]\npub(super) fn open_rw(path: &Path) -> Result<File, DurableError> {\n    let mut options = OpenOptions::new();","sourceCodeStart":189,"sourceCodeEnd":225,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-storage/src/engine/durable/format/mod.rs#L189-L225","documentation":"read_exact_at performs positional (pread-style) reads in a loop until the requested buffer is filled. If positioned_read returns 0 bytes — meaning the file ends before the requested range — it raises UnexpectedEof with this message. Callers include visit_indexed_objects and read_frame_at, which both expect fully readable frames at known offsets.","triggerScenarios":"Reading a frame or indexed object at a recorded offset where the backing file is shorter than offset+length: truncated/corrupted durable log, index entries pointing past EOF, or concurrent truncation of the storage file.","commonSituations":"Durable storage file truncated by a crash or disk-full event; stale index built against a longer earlier version of the file; reading a file copied incompletely; offset overflow bugs leading to reads at bogus positions (though those get the separate overflow error).","solutions":["Verify the file's integrity/rebuild the index — the offset likely points past EOF after truncation","Check available file size before reading: ensure file_len >= offset + buffer_len","Restore the durable file from backup or snapshot if it was truncated by a crash","Re-scan/rebuild visit_indexed_objects against the current file so offsets match reality"],"exampleFix":"// before\nlet frame = reader.read_frame_at(&mut file, offset, len)?;\n// after\nlet file_len = file.metadata()?.len();\nif offset + len as u64 > file_len {\n    return Err(StorageError::TruncatedFile { offset });\n}\nlet frame = reader.read_frame_at(&mut file, offset, len)?;","handlingStrategy":"validation","validationCode":"fn can_read_at(file: &File, offset: u64, len: u64) -> io::Result<bool> {\n    Ok(file.metadata()?.len() >= offset.saturating_add(len))\n}\nif !can_read_at(&file, offset, buf_len)? {\n    return Err(StorageError::TruncatedFile { offset });\n}","typeGuard":null,"tryCatchPattern":"match result {\n    Err(e) if e.kind() == io::ErrorKind::UnexpectedEof\n        && e.to_string() == \"positional read reached end of file\" => {\n        eprintln!(\"durable file truncated or index stale at offset {offset}; rebuild index or restore backup\");\n    }\n    other => other?,\n}","preventionTips":["Validate index offsets against current file length before traversal","Use fsync/fdatasync on write paths so crashes never leave silently truncated files","Detect disk-full conditions during writes and treat truncation as corruption needing recovery"],"tags":["eof","storage","truncated","read"],"backgroundTag":"unexpected-eof","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}