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

  1. Before list(entry), require entry.lcha.is_dir() and not entry.lcha.is_indirect().
  2. For symlinked directories, resolve the target outside the trash API instead of expanding it in place.
  3. 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

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


AI-assisted analysis of sxyazi/yazi@94abcfa92f (2026-08-16). Data as JSON: /api/errors/be6ef04c12e5657b. Report an issue: GitHub.