sxyazi/yazi · error · io::Error

trash item outside of trash folders

Error message

trash item outside of trash folders

What it means

`Trash::entry(id)` (freedesktop/trash.rs:40) parses the `.trashinfo` file behind a `TrashId` and then verifies that its trash root is one of the currently known trash folders (`os_limited::trash_folders()`). If the root is not in that set it returns `ErrorKind::NotFound`, `trash item outside of trash folders` — the id refers to a trash that this host does not currently recognize.

Source

Thrown at yazi-fs/src/trash/freedesktop/trash.rs:40

			return Err(io::Error::new(io::ErrorKind::InvalidInput, "trash item is not a directory"));
		}

		fs::read_dir(&entry.backing)?
			.map(|dent| {
				let dent = dent?;
				entry.child(dent.file_name())
			})
			.collect()
	}

	pub(crate) fn entry(&self, id: &TrashId) -> io::Result<TrashEntry> {
		let info = TrashInfo::parse(id.top())?;
		if !os_limited::trash_folders()
			.map_err(io::Error::other)?
			.iter()
			.any(|folder| folder == &info.root)
		{
			return Err(io::Error::new(io::ErrorKind::NotFound, "trash item outside of trash folders"));
		}

		if id.has_rel() && !fs::symlink_metadata(&info.backing)?.file_type().is_dir() {
			return Err(io::Error::new(io::ErrorKind::InvalidInput, "trash item is not a directory"));
		}

		let (backing, original) = if id.has_rel() {
			(info.backing.join(id.rel()), info.original.join(id.rel()))
		} else {
			(info.backing, info.original)
		};
		TrashEntry::new(id.clone(), backing, Some(original))
	}

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

View on GitHub (pinned to 94abcfa92f)

Solutions

  1. Re-list trash (`Trash::list(None)` / `tops()`) to get fresh, currently-valid ids instead of reusing cached ones.
  2. Remount the external drive that owns the trash root, then retry.
  3. If the `.trashinfo` is orphaned junk, delete it so the id can no longer be produced.

Example fix

// before
let entry = trash.entry(&cached_id)?; // NotFound after drive unmount

// after
match trash.entry(&cached_id) {
    Ok(entry) => { /* proceed */ }
    Err(e) if e.kind() == io::ErrorKind::NotFound => {
        let fresh: Vec<_> = trash.list(None)?; // rebuild from current trash folders
    }
    Err(e) => return Err(e),
}
Defensive patterns

Strategy: try-catch

Validate before calling

use std::path::Path;

fn in_known_trash_folder(info_path: &Path, folders: &[std::path::PathBuf]) -> bool {
    // root = info_path.parent().filter(name=="info").and_then(parent)
    info_path
        .parent()
        .filter(|p| p.file_name().map(|n| n == "info").unwrap_or(false))
        .and_then(std::path::Path::parent)
        .is_some_and(|root| folders.iter().any(|f| f == root))
}

Try / catch

match trash.entry(&id) {
    Ok(e) => { /* ... */ }
    Err(e) if e.kind() == io::ErrorKind::NotFound => {
        // stale id: discard and re-list via trash.list(None)
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: A stale `TrashId` whose top-level `.trashinfo` lives under a trash root that is no longer enumerated: an external drive's `.Trash/1000` after the drive was unmounted, a `$XDG_DATA_HOME/Trash` that moved, or a hand-built id pointing at an arbitrary `.trashinfo` path.

Common situations: Restoring from a trash listing captured before a removable drive was unplugged; home dir moved between machines; `$XDG_DATA_HOME` changed since the id was created.

Related errors


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