sxyazi/yazi · warning · anyhow::Error
Destination already exists
Error message
Destination already exists
What it means
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.
Source
Thrown at yazi-actor/src/mgr/bulk_rename.rs:126
return Ok(());
}
if !Self::ask_continue(&todo, decision)? {
return Ok(());
}
let permit = WATCHER.acquire().await.unwrap();
let (mut failed, mut succeeded) = (Vec::new(), HashMap::with_capacity(todo.len()));
for (o, n) in todo {
let (Ok(old), Ok(new)) =
(Self::replace_url(&selected[o.0], root, &o), Self::replace_url(&selected[n.0], root, &n))
else {
failed.push((o, n, anyhow!("Invalid new or old file name")));
continue;
};
if maybe_exists(&new).await && !engine::must_identical(&old, &new).await {
failed.push((o, n, anyhow!("Destination already exists")));
} else if let Err(e) = engine::rename(&old, &new).await {
failed.push((o, n, e.into()));
} else if let Ok(f) = engine::file(new).await {
succeeded.insert(old, f);
} else {
failed.push((o, n, anyhow!("Failed to retrieve file info")));
}
}
if !succeeded.is_empty() {
let it = succeeded.iter().map(|(o, n)| (o.as_url(), n.url.as_url()));
log_if_err!(Pubsub::pub_after_bulk_rename(it));
FilesOp::rename(succeeded);
}
drop(permit);
if !failed.is_empty() {
Self::output_failed(failed).await?;View on GitHub (pinned to 94abcfa92f)
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
Example fix
// before (single batch, banner.jpg already exists) photo.jpg -> banner.jpg // after (two steps) banner.jpg -> banner.old.jpg photo.jpg -> banner.jpg
Defensive patterns
Strategy: validation
Validate before calling
// Pre-flight the whole batch for collisions:
for (o, n) in &todo {
let (Ok(old), Ok(new)) = (replace(o), replace(n)) else { continue };
if maybe_exists(&new).await && !engine::must_identical(&old, &new).await {
return Err(anyhow!("destination exists: {}", new));
}
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Invalid new or old file name
- Failed to retrieve file info
- Not a local URL: {url:?}
- restore target already exists: {to:?}
- trash item has no put-back location
AI-assisted analysis of sxyazi/yazi@94abcfa92f (2026-08-16).
Data as JSON: /api/errors/38d8cd297064b543.
Report an issue: GitHub.