{"record":{"id":"321bec8f693fdf44","repo":"vectordotdev/vector","slug":"cannot-extend-a-file-through-the-truncation-api","errorCode":null,"errorMessage":"cannot extend a file through the truncation API","messagePattern":"cannot extend a file through the truncation API","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"lib/vector-buffers/src/variants/disk_v2/io.rs","lineNumber":302,"sourceCode":"/// Builds a set of `OpenOptions` for opening a file as readable.\nfn open_readable_file_options() -> OpenOptions {\n    let mut open_options = OpenOptions::new();\n    open_options.read(true);\n    open_options\n}\n\nimpl AsyncFile for tokio::fs::File {\n    async fn metadata(&self) -> io::Result<Metadata> {\n        let metadata = self.metadata().await?;\n        Ok(Metadata {\n            len: metadata.len(),\n        })\n    }\n\n    async fn truncate(&self, size: u64) -> io::Result<()> {\n        let current_size = self.metadata().await?.len();\n        if size > current_size {\n            return Err(io::Error::new(\n                io::ErrorKind::InvalidInput,\n                \"cannot extend a file through the truncation API\",\n            ));\n        }\n\n        self.set_len(size).await\n    }\n\n    async fn sync_all(&self) -> io::Result<()> {\n        self.sync_all().await\n    }\n}\n\nimpl ReadableMemoryMap for memmap2::Mmap {}\n\nimpl ReadableMemoryMap for memmap2::MmapMut {}\n\nimpl WritableMemoryMap for memmap2::MmapMut {","sourceCodeStart":284,"sourceCodeEnd":320,"githubUrl":"https://github.com/vectordotdev/vector/blob/bdb87aeaa4c4ff27c0ba643c1c77b21bf2ef4013/lib/vector-buffers/src/variants/disk_v2/io.rs#L284-L320","documentation":"The disk v2 buffer's file abstraction exposes truncate only to shrink a file. This error is raised by truncate when the requested size is larger than the file's current length, since growing through the truncation API is not supported. It is returned as an InvalidInput io error.","triggerScenarios":"Calling File::truncate(size) with size > current file length; a caller computing a truncation target from stale metadata after the file has grown or been compacted.","commonSituations":"Buffer recovery/compaction logic racing with concurrent writes; a bug or off-by-one in code computing the retained byte offset.","solutions":["Only call truncate with a size <= the file's current length; check metadata().len() first.","If growth is intended, use the file's write/extend path instead of truncate.","Re-fetch current size immediately before truncating to avoid acting on stale metadata."],"exampleFix":"// before\nfile.truncate(new_size).await?; // new_size may exceed len\n// after\nlet len = file.metadata().await?.len();\nif new_size <= len {\n    file.truncate(new_size).await?;\n}","handlingStrategy":"validation","validationCode":"// guard truncation calls\nasync fn safe_truncate(file: &BufferFile, size: u64) -> io::Result<()> {\n    let len = file.metadata().await?.len();\n    if size > len {\n        return Err(io::Error::new(io::ErrorKind::InvalidInput, \"size exceeds file length\"));\n    }\n    file.truncate(size).await\n}","typeGuard":null,"tryCatchPattern":"match file.truncate(size).await {\n    Err(e) if e.kind() == io::ErrorKind::InvalidInput => {\n        // tried to grow via truncate; use write path instead\n    }\n    other => other?,\n}","preventionTips":["Re-read file length immediately before truncating.","Route all growth through the write path, never truncate.","Add unit tests for truncate-at-boundary sizes."],"tags":["buffers","disk","io","truncate"],"backgroundTag":"invalid-argument-value","analyzedSha":"bdb87aeaa4c4ff27c0ba643c1c77b21bf2ef4013","analyzedAt":"2026-09-16T02:53:35.741Z","contentChangedAt":"2026-09-16T02:53:35.741Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}