{"record":{"id":"40b8ebb70b320012","repo":"sxyazi/yazi","slug":"trash-item-is-not-a-directory-40b8eb","errorCode":null,"errorMessage":"trash item is not a directory","messagePattern":"trash item is not a directory","errorType":"validation","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"yazi-fs/src/trash/macos/trash.rs","lineNumber":15,"sourceCode":"use std::{fs, io, path::{Path, PathBuf}};\n\nuse yazi_macro::ok_or_not_found;\n\nuse super::{super::{TrashCha, TrashEntries, TrashEntry, TrashId, restore_item}, DsStore};\nuse crate::{cha::Cha, 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\tif entry.is_some_and(|entry| !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\tlet root = self.root()?;\n\t\tlet store = if entry.is_none() {\n\t\t\tDsStore::parse(&root.join(\".DS_Store\")).unwrap_or_default()\n\t\t} else {\n\t\t\tDefault::default()\n\t\t};\n\n\t\tlet path = entry.map_or(&root, |entry| &entry.backing);\n\t\tlet it = match fs::read_dir(path) {\n\t\t\tOk(it) => it,\n\t\t\tErr(e) if e.kind() == io::ErrorKind::NotFound && entry.is_none() => return Ok(vec![]),\n\t\t\tErr(e) => return Err(e),\n\t\t};\n\n\t\tit.map(|dent| {\n\t\t\tlet dent = dent?;","sourceCodeStart":1,"sourceCodeEnd":33,"githubUrl":"https://github.com/sxyazi/yazi/blob/94abcfa92f4ad3f0a1aef6c1ea083cfb8aa6c8c2/yazi-fs/src/trash/macos/trash.rs#L1-L33","documentation":"Returned by Trash::list on macOS when it is called with Some(entry) whose lcha (link metadata) says the item is not a directory. Trash listing is hierarchical: only directories can be expanded, so asking for the children of a trashed file is an invalid-input error (io::ErrorKind::InvalidInput).","triggerScenarios":"Trash:list(entry) / the Lua Trash:list with a file entry, e.g. pressing enter or expanding a regular file (or a symlink to a file, since lcha is the non-followed metadata) inside the trash view.","commonSituations":"Plugin or keymap that unconditionally calls list on the hovered trash item; hover state raced (entry became a file after a revalidate); trying to 'open' a file in trash as if it were a folder.","solutions":["Only call list with nil (trash root) or with an entry whose lcha.is_dir() is true.","In UI flows, gate 'enter directory' on the entry kind, not on the current tab mode.","Re-fetch the entry (Trash:entry) if the cached lcha may be stale, then re-check is_dir()."],"exampleFix":"-- Lua: before\nlocal items = ya.fs(\"trash\"):list(entry)\n-- after\nlocal trash = ya.fs(\"trash\")\nlocal items = entry and entry.lcha.is_dir and trash:list(entry) or trash:list()","handlingStrategy":"type-guard","validationCode":"-- Lua: only expand real directories (lcha = non-followed metadata)\nif entry == nil or entry.lcha.is_dir then\n  local items = ya.fs(\"trash\"):list(entry)\nend","typeGuard":"local function is_trash_dir(e)\n  return e ~= nil and e.lcha ~= nil and e.lcha.is_dir == true\nend","tryCatchPattern":"local items, err = ya.fs(\"trash\"):list(entry)\nif items == nil and err.kind == \"invalid-input\" then\n  -- hovered item is a file: open/peek it instead of expanding\nend","preventionTips":["Gate 'enter' in trash tabs on lcha.is_dir, not on tab mode.","Refresh entries with Trash:entry before expanding when the view may be stale.","Remember lcha (not cha) governs traversal: symlinks to dirs are not expandable here."],"tags":["trash","macos","invalid-input","directory","listing"],"backgroundTag":null,"analyzedSha":"94abcfa92f4ad3f0a1aef6c1ea083cfb8aa6c8c2","analyzedAt":"2026-08-16T09:56:24.836Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}