{"record":{"id":"72fb88aa277f074e","repo":"sxyazi/yazi","slug":"trash-directory-is-not-empty","errorCode":null,"errorMessage":"trash directory is not empty","messagePattern":"trash directory is not empty","errorType":"exception","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"yazi-fs/src/trash/windows/trash.rs","lineNumber":91,"sourceCode":"\t\tlet cha = if let Some(entry) = entry {\n\t\t\tTrashSig::item(&self.resolve(entry)?)?\n\t\t} else {\n\t\t\tTrashSig::root()?\n\t\t};\n\n\t\tOk(if cha.hits(current.cha) { None } else { Some(File { cha, ..current.clone() }) })\n\t}\n\n\tpub(crate) fn remove_file(&self, entry: &TrashEntry) -> io::Result<()> {\n\t\tself.resolve(entry)?.delete()\n\t}\n\n\tpub(crate) fn remove_dir(&self, entry: &TrashEntry) -> io::Result<()> {\n\t\tlet item = self.resolve(entry)?;\n\t\tif !entry.has_rel() {\n\t\t\titem.delete()\n\t\t} else if !item.is_empty()? {\n\t\t\tErr(io::Error::new(io::ErrorKind::DirectoryNotEmpty, \"trash directory is not empty\"))\n\t\t} else {\n\t\t\titem.delete()\n\t\t}\n\t}\n\n\tpub(crate) fn restore(&self, entries: TrashEntries) -> io::Result<()> {\n\t\tfor entry in entries {\n\t\t\tlet to = entry.original.as_deref().ok_or_else(|| {\n\t\t\t\tio::Error::new(io::ErrorKind::NotFound, \"trash item has no put-back location\")\n\t\t\t})?;\n\n\t\t\tself.restore_do(&self.resolve(&entry)?, to)?;\n\t\t}\n\t\tOk(())\n\t}\n\n\tpub(crate) fn rename(&self, entry: &TrashEntry, path: &Path) -> io::Result<()> {\n\t\tfs::rename(&entry.backing, path)","sourceCodeStart":73,"sourceCodeEnd":109,"githubUrl":"https://github.com/sxyazi/yazi/blob/94abcfa92f4ad3f0a1aef6c1ea083cfb8aa6c8c2/yazi-fs/src/trash/windows/trash.rs#L73-L109","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\ntrash.remove_dir(&entry).await?;\n\n// after\nfor child in trash.list(Some(&entry))? {\n    if child.lcha.is_dir() { trash.remove_dir(&child).await?; }\n    else { trash.remove_file(&child).await?; }\n}\ntrash.remove_dir(&entry).await?;","handlingStrategy":"fallback","validationCode":"// Rust: pre-check emptiness\nlet is_empty = trash.list(Some(&entry)).map(|c| c.is_empty()).unwrap_or(false);\nif is_empty { trash.remove_dir(&entry)?; } else { /* recurse or empty() */ }","typeGuard":null,"tryCatchPattern":"match trash.remove_dir(&entry) {\n    Err(e) if e.kind() == io::ErrorKind::DirectoryNotEmpty => {\n        for child in trash.list(Some(&entry))? { /* remove child first */ }\n        trash.remove_dir(&entry)?;\n    }\n    other => other?,\n}","preventionTips":["Remove trash children before their parent","Use trash.empty() to purge the whole bin instead of nested deletes","Order bulk deletions depth-first"],"tags":["windows","trash","recycle-bin","delete"],"backgroundTag":null,"analyzedSha":"94abcfa92f4ad3f0a1aef6c1ea083cfb8aa6c8c2","analyzedAt":"2026-08-16T09:56:24.836Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}