sxyazi/yazi · error · io::Error
trash directory is not empty
Error message
trash directory is not empty
What it means
Thrown by `Trash::remove_dir` on Windows when deleting a nested trashed directory (an entry with `has_rel()`) that still contains children. The implementation deletes top-level Recycle Bin items via the shell API directly, but nested directories must be empty before `item.delete()` is attempted — yazi does not recursively purge nested trash content on your behalf here.
Source
Thrown at yazi-fs/src/trash/windows/trash.rs:91
let cha = if let Some(entry) = entry {
TrashSig::item(&self.resolve(entry)?)?
} else {
TrashSig::root()?
};
Ok(if cha.hits(current.cha) { None } else { Some(File { cha, ..current.clone() }) })
}
pub(crate) fn remove_file(&self, entry: &TrashEntry) -> io::Result<()> {
self.resolve(entry)?.delete()
}
pub(crate) fn remove_dir(&self, entry: &TrashEntry) -> io::Result<()> {
let item = self.resolve(entry)?;
if !entry.has_rel() {
item.delete()
} else if !item.is_empty()? {
Err(io::Error::new(io::ErrorKind::DirectoryNotEmpty, "trash directory is not empty"))
} else {
item.delete()
}
}
pub(crate) fn restore(&self, entries: TrashEntries) -> io::Result<()> {
for entry in entries {
let to = entry.original.as_deref().ok_or_else(|| {
io::Error::new(io::ErrorKind::NotFound, "trash item has no put-back location")
})?;
self.restore_do(&self.resolve(&entry)?, to)?;
}
Ok(())
}
pub(crate) fn rename(&self, entry: &TrashEntry, path: &Path) -> io::Result<()> {
fs::rename(&entry.backing, path)View on GitHub (pinned to 94abcfa92f)
Solutions
- Remove the children first: `trash.list(Some(&entry))` and recursively `remove_file`/`remove_dir` each child, then remove the parent
- To purge everything at once, use `trash.empty()` (SHEmptyRecycleBinW) instead of per-entry removal
- Catch `DirectoryNotEmpty` and re-list/retry once to handle races with concurrent bin changes
Example fix
// before
trash.remove_dir(&entry).await?;
// after
for child in trash.list(Some(&entry))? {
if child.lcha.is_dir() { trash.remove_dir(&child).await?; }
else { trash.remove_file(&child).await?; }
}
trash.remove_dir(&entry).await?; Defensive patterns
Strategy: fallback
Validate before calling
// Rust: pre-check emptiness
let is_empty = trash.list(Some(&entry)).map(|c| c.is_empty()).unwrap_or(false);
if is_empty { trash.remove_dir(&entry)?; } else { /* recurse or empty() */ } Try / catch
match trash.remove_dir(&entry) {
Err(e) if e.kind() == io::ErrorKind::DirectoryNotEmpty => {
for child in trash.list(Some(&entry))? { /* remove child first */ }
trash.remove_dir(&entry)?;
}
other => other?,
} Prevention
- Remove trash children before their parent
- Use trash.empty() to purge the whole bin instead of nested deletes
- Order bulk deletions depth-first
When it happens
Trigger: Calling `trash.remove_dir(&entry)` where `entry.has_rel()` is true and `item.is_empty()?` returns false — i.e., deleting a subdirectory inside the trash that still has files or folders.
Common situations: Deleting a folder from within the trash panel while its children were never removed; bulk operations that remove parents before children; a race where new children appear between listing and deletion.
Related errors
- trash item has no put-back location
- trash item is not a directory
- bundled rust-lld setup requires a Unix host
- Unsupported OS for trash operation
- restore target already exists: {to:?}
AI-assisted analysis of sxyazi/yazi@94abcfa92f (2026-08-16).
Data as JSON: /api/errors/72fb88aa277f074e.
Report an issue: GitHub.