sxyazi/yazi · error · io::Error

trash item has no put-back location

Error message

trash item has no put-back location

What it means

`Trash::restore` (freedesktop/trash.rs:112) iterates entries and requires each to carry an `original` put-back path; an entry without one yields `ErrorKind::NotFound`, `trash item has no put-back location`. In the freedesktop flow `original` comes from the `Path=` line of the `.trashinfo`, so this fires for entries constructed without that metadata (e.g. entries built from bare `TrashId`s rather than `Trash::entry`).

Source

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

		fs::remove_file(&entry.backing)?;
		if !entry.has_rel() {
			fs::remove_file(entry.top())?;
		}
		Ok(())
	}

	pub(crate) fn remove_dir(&self, entry: &TrashEntry) -> io::Result<()> {
		fs::remove_dir(&entry.backing)?;
		if !entry.has_rel() {
			fs::remove_file(entry.top())?;
		}
		Ok(())
	}

	pub(crate) fn restore(&self, entries: TrashEntries) -> io::Result<()> {
		for entry in entries {
			let to = entry.original.clone().ok_or_else(|| {
				io::Error::new(io::ErrorKind::NotFound, "trash item has no put-back location")
			})?;

			restore_item(&entry.backing, &to)?;

			if !entry.has_rel() {
				fs::remove_file(entry.top())?;
			}
		}
		Ok(())
	}

	// FIXME: also rename or remove the .trashinfo file in the info folder
	pub(crate) fn rename(&self, entry: &TrashEntry, path: &Path) -> io::Result<()> {
		fs::rename(&entry.backing, path)
	}

	pub(crate) fn empty(&self) -> io::Result<()> {
		for entry in self.tops()? {

View on GitHub (pinned to 94abcfa92f)

Solutions

  1. Build restore batches only from entries obtained via `Trash::entry(id)`/`Trash::list`, which carry the parsed original path.
  2. For items already lacking put-back info, restore manually: move the file out of `<root>/files/` to the desired location and delete the orphaned `.trashinfo`.
  3. Filter out `original.is_none()` entries before calling restore so one bad item does not abort the batch.

Example fix

// before
trash::restore(all_entries)?; // aborts on first entry with original == None

// after
let restorable: Vec<_> = all_entries.into_iter().filter(|e| e.original.is_some()).collect();
trash::restore(restorable)?;
// report skipped items to the user for manual restore
Defensive patterns

Strategy: fallback

Validate before calling

let restorable: Vec<_> = entries.into_iter().filter(|e| e.original.is_some()).collect();
let skipped = total - restorable.len(); // report for manual restore

Type guard

fn is_restorable(entry: &TrashEntry) -> bool {
    entry.original.is_some()
}

Try / catch

match trash::restore(entries) {
    Ok(()) => {}
    Err(e) if e.kind() == io::ErrorKind::NotFound => {
        // filter out entries with original == None, retry the rest,
        // then manually move the leftovers out of <root>/files/
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Restoring a `TrashEntries` collection that includes entries created via `TrashEntry::new/top` with `original: None`, or ids for `.trashinfo` files whose `Path=` could not be honored.

Common situations: Plugins/Lua building restore batches from ids alone; trash metadata partially damaged while the backing file still exists.

Related errors


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