pnpm/pnpm · error · std::io::Error

temporary path creation collided

Error message

temporary path creation collided

What it means

pnpr's storage layer writes files atomically: it exclusively creates a unique sibling temp file (<base>.tmp.<pid>.<counter>.<random>) via create_new (O_EXCL) and later renames it over the destination. This error is returned when all MAX_TEMP_CREATE_ATTEMPTS attempts collided with an existing file (ErrorKind::AlreadyExists). Because the name embeds the pid, a monotonic counter, and fresh random bytes, persistent collision means an external interferer, a deterministic/mocked RNG, or a filesystem that misreports exclusive creates.

Source

Thrown at pnpr/crates/pnpr/src/storage.rs:1024

async fn create_tmp_file_with(
    base: &Path,
    mut next_path: impl FnMut(&Path) -> PathBuf,
) -> Result<(fs::File, PathBuf)> {
    let mut last_already_exists = None;
    for _ in 0..MAX_TEMP_CREATE_ATTEMPTS {
        let tmp_path = next_path(base);
        match fs::OpenOptions::new().read(true).write(true).create_new(true).open(&tmp_path).await {
            Ok(file) => return Ok((file, tmp_path)),
            Err(err) if err.kind() == ErrorKind::AlreadyExists => {
                last_already_exists = Some(err);
            }
            Err(err) => return Err(err.into()),
        }
    }
    Err(last_already_exists
        .unwrap_or_else(|| {
            std::io::Error::new(ErrorKind::AlreadyExists, "temporary path creation collided")
        })
        .into())
}

/// A unique sibling of `base` (`<base>.tmp.<pid>.<counter>.<random>`).
/// Keeping it in `base`'s directory keeps the eventual rename atomic on
/// POSIX. Shared with [`crate::s3`]'s staging path.
pub(crate) fn unique_tmp_path(base: &Path) -> PathBuf {
    let counter = TMP_COUNTER.fetch_add(1, Ordering::Relaxed);
    let pid = std::process::id();
    let mut random = [0u8; 8];
    let random = match getrandom::fill(&mut random) {
        Ok(()) => u64::from_ne_bytes(random),
        Err(_) => 0,
    };
    let mut name = base.file_name().map(std::ffi::OsStr::to_os_string).unwrap_or_default();
    name.push(format!(".tmp.{pid}.{counter}.{random:016x}"));
    match base.parent() {

View on GitHub (pinned to 6261b7f388)

Solutions

  1. Retry the whole write operation: each new attempt mints a fresh <pid>.<counter>.<random> name, so a collision storm is transient
  2. Find and stop the process or test double that pre-creates files matching <base>.tmp.<pid>.* in the target directory
  3. If it reproduces on a network/FUSE mount, verify the volume honors O_EXCL by testing the same write on local disk
  4. If deterministic, suspect a broken getrandom source (mocked in tests) and report it to the pnpr maintainers with a reproduction

Example fix

// before: single shot; a collision storm fails the whole storage write
let blob = storage.put(&key, bytes).await?;

// after: retry the operation — every attempt generates a new temp path
for attempt in 0..3 {
    match storage.put(&key, bytes.clone()).await {
        Ok(blob) => break Ok(blob),
        Err(e) if e.to_string().contains("temporary path creation collided") && attempt < 2 => continue,
        Err(e) => break Err(e),
    }
}?;
Defensive patterns

Strategy: retry

Try / catch

// Rust: treat persistent EEXIST as transient at the call site
match storage.put(&key, bytes).await {
    Ok(v) => v,
    Err(e) if matches!(e.kind(), Some(std::io::ErrorKind::AlreadyExists)) => {
        // names are pid+counter+random; a later attempt collides anew only rarely
        retry_with_backoff(|| storage.put(&key, bytes.clone())).await?
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Every create_new in the loop returns EEXIST: a test harness mocking getrandom deterministically, another process squatting on <base>.tmp.<pid>.* names in the same directory, or a FUSE/network filesystem returning spurious AlreadyExists for exclusive creates.

Common situations: Deterministic RNG substitutes in unit tests; hostile or misbehaving co-tenants writing into the registry storage directory; exotic network/FUSE mounts with broken O_EXCL semantics.

Understand the failure class

Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.

Related errors


AI-assisted analysis of pnpm/pnpm@6261b7f388 (2026-08-17). Data as JSON: /api/errors/3d973254d6b5c733. Report an issue: GitHub.