{"record":{"id":"38d8cd297064b543","repo":"sxyazi/yazi","slug":"destination-already-exists","errorCode":null,"errorMessage":"Destination already exists","messagePattern":"Destination already exists","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"warning","filePath":"yazi-actor/src/mgr/bulk_rename.rs","lineNumber":126,"sourceCode":"\t\t\treturn Ok(());\n\t\t}\n\n\t\tif !Self::ask_continue(&todo, decision)? {\n\t\t\treturn Ok(());\n\t\t}\n\n\t\tlet permit = WATCHER.acquire().await.unwrap();\n\t\tlet (mut failed, mut succeeded) = (Vec::new(), HashMap::with_capacity(todo.len()));\n\t\tfor (o, n) in todo {\n\t\t\tlet (Ok(old), Ok(new)) =\n\t\t\t\t(Self::replace_url(&selected[o.0], root, &o), Self::replace_url(&selected[n.0], root, &n))\n\t\t\telse {\n\t\t\t\tfailed.push((o, n, anyhow!(\"Invalid new or old file name\")));\n\t\t\t\tcontinue;\n\t\t\t};\n\n\t\t\tif maybe_exists(&new).await && !engine::must_identical(&old, &new).await {\n\t\t\t\tfailed.push((o, n, anyhow!(\"Destination already exists\")));\n\t\t\t} else if let Err(e) = engine::rename(&old, &new).await {\n\t\t\t\tfailed.push((o, n, e.into()));\n\t\t\t} else if let Ok(f) = engine::file(new).await {\n\t\t\t\tsucceeded.insert(old, f);\n\t\t\t} else {\n\t\t\t\tfailed.push((o, n, anyhow!(\"Failed to retrieve file info\")));\n\t\t\t}\n\t\t}\n\n\t\tif !succeeded.is_empty() {\n\t\t\tlet it = succeeded.iter().map(|(o, n)| (o.as_url(), n.url.as_url()));\n\t\t\tlog_if_err!(Pubsub::pub_after_bulk_rename(it));\n\t\t\tFilesOp::rename(succeeded);\n\t\t}\n\t\tdrop(permit);\n\n\t\tif !failed.is_empty() {\n\t\t\tSelf::output_failed(failed).await?;","sourceCodeStart":108,"sourceCodeEnd":144,"githubUrl":"https://github.com/sxyazi/yazi/blob/94abcfa92f4ad3f0a1aef6c1ea083cfb8aa6c8c2/yazi-actor/src/mgr/bulk_rename.rs#L108-L144","documentation":"Before each rename, bulk rename checks maybe_exists(&new) and allows it only if old and new resolve to the same inode (engine::must_identical). Otherwise the pair is refused with \"Destination already exists\" — a deliberate collision guard so bulk renames never clobber existing files. The pair is listed in the failed output; the batch continues.","triggerScenarios":"The edited target name matches an existing file in the same directory, including a name produced by another pair in the same batch (swaps ordered badly), or the destination exists on a case-insensitive filesystem for a case-only rename.","commonSituations":"Swapping two filenames in one bulk rename (A->B while B->A); renaming to a name differing only in case on macOS/Windows; leftovers from a previous bulk rename occupying target names.","solutions":["Route the collision through a temporary unique name (A->tmp, B->A, tmp->B) across two runs","Delete or move the colliding existing file, then re-run the batch","On case-insensitive filesystems, split a case-only rename into two steps"],"exampleFix":"// before (single batch, banner.jpg already exists)\nphoto.jpg -> banner.jpg\n\n// after (two steps)\nbanner.jpg -> banner.old.jpg\nphoto.jpg -> banner.jpg","handlingStrategy":"validation","validationCode":"// Pre-flight the whole batch for collisions:\nfor (o, n) in &todo {\n    let (Ok(old), Ok(new)) = (replace(o), replace(n)) else { continue };\n    if maybe_exists(&new).await && !engine::must_identical(&old, &new).await {\n        return Err(anyhow!(\"destination exists: {}\", new));\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Scan the failed list after each bulk rename — collisions name both sides","Do swaps through a temporary unique name in two steps","Beware case-only renames on case-insensitive filesystems"],"tags":["bulk-rename","collision","filesystem","case-sensitivity"],"backgroundTag":null,"analyzedSha":"94abcfa92f4ad3f0a1aef6c1ea083cfb8aa6c8c2","analyzedAt":"2026-08-16T09:56:24.836Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}