sxyazi/yazi · error · io::Error
trash item is not a directory
Error message
trash item is not a directory
What it means
Returned by Trash::list on Windows when it is called with an entry that is not a directory or is 'indirect' (a reparse point — symlink, junction, mount). Children are enumerated through the shell item's original (pre-deletion) path, so expanding requires a real directory with a put-back location; InvalidInput rejects files and reparse points alike.
Source
Thrown at yazi-fs/src/trash/windows/trash.rs:28
static COM: io::Result<Com> = Com::new();
}
pub struct Trash;
impl Trash {
pub(crate) fn new() -> io::Result<Self> {
COM.with(|result| {
result.as_ref().map(|_| Self).map_err(|e| io::Error::new(e.kind(), e.to_string()))
})
}
pub(crate) fn list(&self, entry: Option<&TrashEntry>) -> io::Result<Vec<TrashEntry>> {
let Some(entry) = entry else {
return self.tops();
};
if !entry.lcha.is_dir() || entry.lcha.is_indirect() {
return Err(io::Error::new(io::ErrorKind::InvalidInput, "trash item is not a directory"));
}
let original = entry.original.as_deref().ok_or_else(|| {
io::Error::new(io::ErrorKind::NotFound, "trash item has no put-back location")
})?;
self
.resolve(entry)?
.children()?
.into_iter()
.map(|item| {
let name = item.display_name(SIGDN_PARENTRELATIVEPARSING)?;
item.entry(entry.id.child(&name)?, Some(original.join(&name)))
})
.collect()
}
pub(crate) fn entry(&self, id: &TrashId) -> io::Result<TrashEntry> {View on GitHub (pinned to 94abcfa92f)
Solutions
- Before list(entry), require entry.lcha.is_dir() and not entry.lcha.is_indirect().
- For symlinked directories, resolve the target outside the trash API instead of expanding it in place.
- Re-fetch the entry if metadata may be stale, then re-check.
Example fix
-- Lua: before
local items = ya.fs("trash"):list(entry)
-- after
local e = ya.fs("trash"):entry(entry) -- refresh
local items = e and e.lcha.is_dir and not e.lcha.is_indirect and ya.fs("trash"):list(e) or nil Defensive patterns
Strategy: type-guard
Validate before calling
-- Lua: expand only real, non-reparse directories
if entry == nil or (entry.lcha.is_dir and not entry.lcha.is_indirect) then
local items = ya.fs("trash"):list(entry)
end Type guard
local function is_expandable(e) return e ~= nil and e.lcha ~= nil and e.lcha.is_dir == true and e.lcha.is_indirect ~= true end
Try / catch
local items, err = ya.fs("trash"):list(entry)
if items == nil then
if err.kind == "invalid-input" then -- file or symlink: not expandable
elseif err.kind == "not-found" then -- no put-back location: cannot enumerate children
end
end Prevention
- Check is_dir AND is_indirect on lcha before expanding — symlinks/junctions are rejected even when they target directories.
- Refresh the entry before expanding if the view may be stale.
- Handle the sibling 'no put-back location' NotFound error distinctly.
When it happens
Trigger: Trash:list(entry) where entry.lcha.is_dir() is false or entry.lcha.is_indirect() is true (symlinked folder inside the recycle bin); note the sibling NotFound error at line 31 fires first when original is missing.
Common situations: Expanding a trashed symlink/junction to a directory in the trash tab; plugins calling list on the hovered entry without kind checks; entries restyled after revalidate races.
Related errors
- trash item is not a directory
- invalid trash item path
- invalid trash entry path
- item is not in the trash
- invalid trash entry
AI-assisted analysis of sxyazi/yazi@94abcfa92f (2026-08-16).
Data as JSON: /api/errors/be6ef04c12e5657b.
Report an issue: GitHub.