{"record":{"id":"8e74d7b09259f5e9","repo":"sxyazi/yazi","slug":"trash-item-is-not-a-directory","errorCode":null,"errorMessage":"trash item is not a directory","messagePattern":"trash item is not a directory","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"yazi-fs/src/trash/freedesktop/trash.rs","lineNumber":21,"sourceCode":"use trash::os_limited;\nuse yazi_macro::ok_or_not_found;\nuse yazi_shim::Twox128;\n\nuse super::{super::{TrashCha, TrashEntries, TrashEntry, TrashId, restore_item}, TrashInfo};\nuse crate::{cha::{Cha, ChaSig}, file::File};\n\npub struct Trash;\n\nimpl Trash {\n\tpub(crate) fn new() -> io::Result<Self> { Ok(Self) }\n\n\tpub(crate) fn list(&self, entry: Option<&TrashEntry>) -> io::Result<Vec<TrashEntry>> {\n\t\tlet Some(entry) = entry else {\n\t\t\treturn self.tops();\n\t\t};\n\n\t\tif !entry.lcha.is_dir() {\n\t\t\treturn Err(io::Error::new(io::ErrorKind::InvalidInput, \"trash item is not a directory\"));\n\t\t}\n\n\t\tfs::read_dir(&entry.backing)?\n\t\t\t.map(|dent| {\n\t\t\t\tlet dent = dent?;\n\t\t\t\tentry.child(dent.file_name())\n\t\t\t})\n\t\t\t.collect()\n\t}\n\n\tpub(crate) fn entry(&self, id: &TrashId) -> io::Result<TrashEntry> {\n\t\tlet info = TrashInfo::parse(id.top())?;\n\t\tif !os_limited::trash_folders()\n\t\t\t.map_err(io::Error::other)?\n\t\t\t.iter()\n\t\t\t.any(|folder| folder == &info.root)\n\t\t{\n\t\t\treturn Err(io::Error::new(io::ErrorKind::NotFound, \"trash item outside of trash folders\"));","sourceCodeStart":3,"sourceCodeEnd":39,"githubUrl":"https://github.com/sxyazi/yazi/blob/a541adde4e5b082719a0f21a87eb3eab071ece58/yazi-fs/src/trash/freedesktop/trash.rs#L3-L39","documentation":"The freedesktop Trash::list lists the children of a trashed directory entry. Because listing children of a regular file is meaningless (and marked TODO in the source), it rejects non-directory entries up-front with InvalidInput \"trash item is not a directory\". Only trashed directories can be expanded into child entries.","triggerScenarios":"Calling list() with a trash entry that points to a trashed file rather than a trashed directory — e.g. asking the trash provider to enumerate children of \"cat.jpg\".","commonSituations":"A UI or plugin navigates into a trashed file as if it were a folder; stale UI state shows a deleted file as expandable; code iterates all trash entries and calls list() on each without filtering directories.","solutions":["Check entry.lcha.is_dir() (or the entry's kind) before calling list(); skip files or emit them as leaves.","Use the flat trash listing (tops()) and handle files and directories differently in the caller.","Catch io::ErrorKind::InvalidInput from list() and treat it as an empty children set for file entries."],"exampleFix":"// before\nlet children = trash.list(&entry).await?;\n// after\nlet children = if entry.lcha.is_dir() {\n    trash.list(&entry).await?\n} else {\n    vec![] // files have no children\n};","handlingStrategy":"type-guard","validationCode":"// Rust\nif !entry.lcha.is_dir() { return Ok(vec![]); }","typeGuard":"// Rust\nfn is_trashed_dir(entry: &TrashEntry) -> bool { entry.lcha.is_dir() }","tryCatchPattern":"let children = match trash.list(&entry).await {\n    Err(e) if e.kind() == std::io::ErrorKind::InvalidInput => Vec::new(), // file entry\n    other => other?,\n};","preventionTips":["Check is_dir on the entry before enumerating children.","Treat trashed files as leaves in tree UIs.","Refresh trash state after every restore/empty operation."],"tags":["trash","freedesktop","not-a-directory","io"],"backgroundTag":"trash-item-not-a-directory","analyzedSha":"a541adde4e5b082719a0f21a87eb3eab071ece58","analyzedAt":"2026-09-18T10:12:31.659Z","contentChangedAt":"2026-09-18T10:12:31.659Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}