{"record":{"id":"c0bfedcf2b4530f2","repo":"pnpm/pnpm","slug":"local-tarball-changed-while-reading-refused-to-re","errorCode":null,"errorMessage":"local tarball changed while reading; refused to read past {size} bytes","messagePattern":"local tarball changed while reading; refused to read past (.+?) bytes","errorType":"exception","errorClass":"TarballError::ReadLocalTarball","httpStatus":null,"severity":"error","filePath":"pnpm/crates/tarball/src/local_tarball.rs","lineNumber":69,"sourceCode":"    size: u64,\n) -> Result<Vec<u8>, TarballError> {\n    use tokio::io::AsyncReadExt;\n\n    let read_limit = size.checked_add(1).ok_or_else(|| {\n        read_local_tarball_error(\n            path,\n            io::ErrorKind::InvalidData,\n            format!(\"local tarball is too large to read into memory ({size} bytes)\"),\n        )\n    })?;\n    let mut buffer = allocate_local_tarball_buffer(path, package_url, size)?;\n    let mut reader = file.take(read_limit);\n    reader\n        .read_to_end(&mut buffer)\n        .await\n        .map_err(|source| TarballError::ReadLocalTarball { path: path.to_path_buf(), source })?;\n    if u64::try_from(buffer.len()).unwrap_or(u64::MAX) > size {\n        return Err(read_local_tarball_error(\n            path,\n            io::ErrorKind::InvalidData,\n            format!(\"local tarball changed while reading; refused to read past {size} bytes\"),\n        ));\n    }\n    Ok(buffer)\n}\n\npub(crate) fn allocate_local_tarball_buffer(\n    path: &Path,\n    package_url: &str,\n    size: u64,\n) -> Result<Vec<u8>, TarballError> {\n    allocate_tarball_buffer(Some(size), package_url).map_err(|error| match error {\n        TarballError::TarballTooLarge { .. } => read_local_tarball_error(\n            path,\n            io::ErrorKind::InvalidData,\n            format!(\"local tarball is too large to read into memory ({size} bytes)\"),","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/pnpm/pnpm/blob/6261b7f388016d57ca6b90340342411cd1d0d00f/pnpm/crates/tarball/src/local_tarball.rs#L51-L87","documentation":"The local-tarball reader caps the read at the stat-reported size plus one byte (take(size+1)); if more than `size` bytes were actually read, the file must have grown between the stat that produced `size` and the read itself. This InvalidData error refuses to read past the recorded size, treating the file as modified concurrently (a TOCTOU guard) rather than returning a mix of old and new bytes.","triggerScenarios":"The tarball is rewritten while pnpm reads it: a build step re-packing the .tgz during install, a CI artifact still being written when install starts, or a watch loop regenerating the file.","commonSituations":"CI pipelines where packaging and install stages race; local dev with a pack-on-save watcher; artifact synced over network while consumed.","solutions":["Finish generating/re-packing the tarball before starting the install (add a dependency between the build and install steps)","Retry the install after the file has settled","Write new artifacts to versioned/immutable filenames instead of overwriting one path in place"],"exampleFix":"# before (CI): pack and install race\n- run: pnpm -C libs/ui pack & pnpm install\n\n# after: sequenced\n- run: pnpm -C libs/ui pack\n- run: pnpm install","handlingStrategy":"retry","validationCode":"fn tarball_stable(path: &Path) -> bool {\n    let a = std::fs::metadata(path).map(|m| (m.len(), m.modified().ok()));\n    std::thread::sleep(std::time::Duration::from_millis(50));\n    let b = std::fs::metadata(path).map(|m| (m.len(), m.modified().ok()));\n    a.is_ok() && a == b\n}","typeGuard":"fn is_changed_during_read(err: &TarballError) -> bool {\n    matches!(err, TarballError::ReadLocalTarball { source, .. }\n        if source.kind() == std::io::ErrorKind::InvalidData\n            && source.to_string().contains(\"changed while reading\"))\n}","tryCatchPattern":"match read_local_tarball(path).await {\n    Err(e) if is_changed_during_read(&e) => read_local_tarball(path).await, // re-stat + re-read once settled\n    other => other,\n}","preventionTips":["Sequence pack before install in CI; never overwrite a .tgz in place while installs run","Publish artifacts under immutable, versioned filenames"],"tags":["tarball","race-condition","local-dependency","rust"],"backgroundTag":"file-modified-during-read","analyzedSha":"6261b7f388016d57ca6b90340342411cd1d0d00f","analyzedAt":"2026-08-17T18:30:54.750Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}