sxyazi/yazi · error · io::Error

item is not in the trash

Error message

item is not in the trash

What it means

Returned by Trash::entry on macOS when the TrashId's top path is not a direct child of the trash root (~/.Trash). The macOS backend models trash as top-level items under ~/.Trash plus a relative path inside them; an id whose top lives anywhere else is rejected with io::ErrorKind::InvalidInput.

Source

Thrown at yazi-fs/src/trash/macos/trash.rs:50

		it.map(|dent| {
			let dent = dent?;
			if let Some(entry) = entry {
				entry.child(dent.file_name())
			} else {
				let path = dent.path();
				let original = store
					.get(path.file_name().unwrap_or_default())
					.and_then(|ds| ds.join(Path::new("")).ok());
				TrashEntry::top(path.clone(), path, original)
			}
		})
		.collect()
	}

	pub(crate) fn entry(&self, id: &TrashId) -> io::Result<TrashEntry> {
		let root = self.root()?;
		if id.top().parent() != Some(&root) {
			return Err(io::Error::new(io::ErrorKind::InvalidInput, "item is not in the trash"));
		}
		if id.has_rel() && !fs::symlink_metadata(id.top())?.file_type().is_dir() {
			return Err(io::Error::new(io::ErrorKind::InvalidInput, "trash item is not a directory"));
		}

		let store = DsStore::parse(&root.join(".DS_Store")).unwrap_or_default();
		let name = id.top().file_name().unwrap_or_default();
		let original = store.get(name).and_then(|ds| ds.join(id.rel()).ok());

		TrashEntry::new(id.clone(), id.path(), original)
	}

	pub(crate) fn metadata(&self, entry: &TrashEntry, follow: bool) -> io::Result<Cha> {
		Ok(if follow { entry.cha } else { entry.lcha })
	}

	pub(crate) fn revalidate(
		&self,

View on GitHub (pinned to 94abcfa92f)

Solutions

  1. Always obtain ids from Trash:list/Trash:entry results instead of constructing them manually.
  2. If building by hand, set top to an absolute path directly inside ~/.Trash and put deeper segments in rel.
  3. Verify HOME is stable (dirs::home_dir must resolve to the same absolute root) across the call.

Example fix

-- Lua: before (nested path as top)
local e = ya.fs("trash"):entry({ top = "/Users/me/.Trash/Project/a.txt", rel = "" })
-- after (top is direct child of ~/.Trash, rest is rel)
local e = ya.fs("trash"):entry({ top = "/Users/me/.Trash/Project", rel = "a.txt" })
Defensive patterns

Strategy: validation

Validate before calling

-- Lua: verify top is a direct child of ~/.Trash before lookup
local home = ya.claim_to_be_home -- or obtain HOME from ya
local root = "/Users/" .. ya.user_name .. "/.Trash" -- better: reuse top from a listed entry
local function top_in_trash(top)
  return top:sub(1, #root + 1) == root .. "/" and not top:find("/", #root + 2)
end

Type guard

local function is_valid_trash_id(t)
  return type(t) == "table"
    and type(t.top) == "string" and #t.top > 0
    and type(t.rel) == "string" and t.rel:sub(1, 1) ~= "/"
end

Try / catch

local e, err = ya.fs("trash"):entry(id)
if e == nil and err.kind == "invalid-input" then
  -- id was hand-built: re-list and match by name instead of retrying
end

Prevention

When it happens

Trigger: Calling Trash:entry with a hand-built Lua table { top = '/Users/me/some/path', rel = '' } where top.parent() != ~/.Trash; using a filesystem path instead of an id obtained from Trash:list; HOME resolution changed between listing and lookup so the roots disagree.

Common situations: Plugins constructing ids from Urls or strings instead of reusing entries from list(); ids persisted across sessions after HOME moved; passing a nested path (e.g. ~/.Trash/dir/file) as top instead of top=~/.Trash/dir with rel=file.

Related errors


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