sxyazi/yazi · error · io::Error

invalid trash item path

Error message

invalid trash item path

What it means

`TrashEntry::new` (yazi-fs/src/trash/entry.rs:43) needs a display/stat name for the entry and tries three sources in order: the file name of the id's relative path, the original put-back path's file name, then the backing path's file name. If all three are missing or empty (e.g. paths ending in `..` or empty), it fails with `ErrorKind::InvalidInput`, `invalid trash item path`.

Source

Thrown at yazi-fs/src/trash/entry.rs:43

	fn deref(&self) -> &Self::Target { &self.id }
}

impl TrashEntry {
	#[cfg(trash_unix)]
	pub(super) fn new<B>(id: TrashId, backing: B, original: Option<PathBuf>) -> io::Result<Self>
	where
		B: Into<PathBuf>,
	{
		use super::TrashCha;
		let backing = backing.into();

		let name = id
			.rel()
			.file_name()
			.or_else(|| original.as_deref().and_then(Path::file_name))
			.or_else(|| backing.file_name())
			.filter(|name| !name.is_empty())
			.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "invalid trash item path"))?;

		let (lcha, cha) = Cha::from_trash(&backing, name)?;
		let link_to = if lcha.is_link() { std::fs::read_link(&backing).ok() } else { None };

		Ok(Self { id, cha, lcha, original, link_to, backing })
	}

	#[cfg(trash_unix)]
	pub(super) fn top<T, B>(top: T, backing: B, original: Option<PathBuf>) -> io::Result<Self>
	where
		T: Into<PathBuf>,
		B: Into<PathBuf>,
	{
		Self::new(TrashId::new(top, PathBuf::new())?, backing, original)
	}

	#[cfg(trash_unix)]
	pub(super) fn child(&self, name: OsString) -> io::Result<Self> {

View on GitHub (pinned to 94abcfa92f)

Solutions

  1. Only build entries from ids produced by the trash implementation itself (`Trash::list`/`entry`), never synthesize ids by hand.
  2. Validate `id.rel()` is a plain relative path with a non-empty final component before calling.
  3. Purge the damaged trash item (`rm` the backing file and its `.trashinfo`) so listing stops constructing it.

Example fix

// before
let entry = TrashEntry::new(id, backing, None)?; // rel == ".." -> InvalidInput

// after
let ok = id.rel().file_name().is_some_and(|n| !n.is_empty())
    || backing.file_name().is_some_and(|n| !n.is_empty());
if !ok { /* skip this damaged item */ }
let entry = TrashEntry::new(id, backing, None)?;
Defensive patterns

Strategy: validation

Validate before calling

fn has_any_name(id: &TrashId, original: Option<&std::path::Path>, backing: &std::path::Path) -> bool {
    let nonempty = |n: Option<&std::ffi::OsStr>| n.is_some_and(|n| !n.is_empty());
    nonempty(id.rel().file_name())
        || original.and_then(std::path::Path::file_name).is_some_and(|n| !n.is_empty())
        || nonempty(backing.file_name())
}

Type guard

fn is_usable_trash_id(id: &TrashId, backing: &std::path::Path) -> bool {
    id.rel().file_name().is_some_and(|n| !n.is_empty())
        || backing.file_name().is_some_and(|n| !n.is_empty())
}

Try / catch

match TrashEntry::new(id, backing, original) {
    Ok(e) => { /* ... */ }
    Err(e) if e.kind() == io::ErrorKind::InvalidInput => { /* drop the damaged id; continue listing */ }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Building a `TrashEntry` from a `TrashId` whose `rel()` ends in `..` or is degenerate, while `original` is None and `backing` has no file name (e.g. `/` or `foo/..`). Also reachable via `TrashEntry::child()` when the child name is `..`-like.

Common situations: Hand-constructed or stale trash ids from plugins/Lua; trash metadata damaged so no original path is known and the backing file was removed.

Related errors


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