{"record":{"id":"3d973254d6b5c733","repo":"pnpm/pnpm","slug":"temporary-path-creation-collided","errorCode":null,"errorMessage":"temporary path creation collided","messagePattern":"temporary path creation collided","errorType":"exception","errorClass":"std::io::Error","httpStatus":null,"severity":"error","filePath":"pnpr/crates/pnpr/src/storage.rs","lineNumber":1024,"sourceCode":"\nasync fn create_tmp_file_with(\n    base: &Path,\n    mut next_path: impl FnMut(&Path) -> PathBuf,\n) -> Result<(fs::File, PathBuf)> {\n    let mut last_already_exists = None;\n    for _ in 0..MAX_TEMP_CREATE_ATTEMPTS {\n        let tmp_path = next_path(base);\n        match fs::OpenOptions::new().read(true).write(true).create_new(true).open(&tmp_path).await {\n            Ok(file) => return Ok((file, tmp_path)),\n            Err(err) if err.kind() == ErrorKind::AlreadyExists => {\n                last_already_exists = Some(err);\n            }\n            Err(err) => return Err(err.into()),\n        }\n    }\n    Err(last_already_exists\n        .unwrap_or_else(|| {\n            std::io::Error::new(ErrorKind::AlreadyExists, \"temporary path creation collided\")\n        })\n        .into())\n}\n\n/// A unique sibling of `base` (`<base>.tmp.<pid>.<counter>.<random>`).\n/// Keeping it in `base`'s directory keeps the eventual rename atomic on\n/// POSIX. Shared with [`crate::s3`]'s staging path.\npub(crate) fn unique_tmp_path(base: &Path) -> PathBuf {\n    let counter = TMP_COUNTER.fetch_add(1, Ordering::Relaxed);\n    let pid = std::process::id();\n    let mut random = [0u8; 8];\n    let random = match getrandom::fill(&mut random) {\n        Ok(()) => u64::from_ne_bytes(random),\n        Err(_) => 0,\n    };\n    let mut name = base.file_name().map(std::ffi::OsStr::to_os_string).unwrap_or_default();\n    name.push(format!(\".tmp.{pid}.{counter}.{random:016x}\"));\n    match base.parent() {","sourceCodeStart":1006,"sourceCodeEnd":1042,"githubUrl":"https://github.com/pnpm/pnpm/blob/6261b7f388016d57ca6b90340342411cd1d0d00f/pnpr/crates/pnpr/src/storage.rs#L1006-L1042","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Retry the whole write operation: each new attempt mints a fresh <pid>.<counter>.<random> name, so a collision storm is transient","Find and stop the process or test double that pre-creates files matching <base>.tmp.<pid>.* in the target directory","If it reproduces on a network/FUSE mount, verify the volume honors O_EXCL by testing the same write on local disk","If deterministic, suspect a broken getrandom source (mocked in tests) and report it to the pnpr maintainers with a reproduction"],"exampleFix":"// before: single shot; a collision storm fails the whole storage write\nlet blob = storage.put(&key, bytes).await?;\n\n// after: retry the operation — every attempt generates a new temp path\nfor attempt in 0..3 {\n    match storage.put(&key, bytes.clone()).await {\n        Ok(blob) => break Ok(blob),\n        Err(e) if e.to_string().contains(\"temporary path creation collided\") && attempt < 2 => continue,\n        Err(e) => break Err(e),\n    }\n}?;","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Rust: treat persistent EEXIST as transient at the call site\nmatch storage.put(&key, bytes).await {\n    Ok(v) => v,\n    Err(e) if matches!(e.kind(), Some(std::io::ErrorKind::AlreadyExists)) => {\n        // names are pid+counter+random; a later attempt collides anew only rarely\n        retry_with_backoff(|| storage.put(&key, bytes.clone())).await?\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Keep temp-name generation seeded from the OS CSPRNG (never mock getrandom in shared test fixtures)","Do not pre-create files matching another writer's <base>.tmp.* pattern in shared directories","Prefer local filesystems for the registry storage layer; network mounts weaken O_EXCL guarantees","Bound retries with backoff instead of failing on the first collision storm"],"tags":["rust","filesystem","temp-file","atomic-write","pnpr","file-exists"],"backgroundTag":"file-already-exists","analyzedSha":"6261b7f388016d57ca6b90340342411cd1d0d00f","analyzedAt":"2026-08-17T18:30:54.750Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}