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
- Re-list trash (`Trash::list(None)` / `tops()`) to get fresh, currently-valid ids instead of reusing cached ones.
- Remount the external drive that owns the trash root, then retry.
- 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
- Treat trash ids as session-scoped; re-list after mounts change instead of caching ids.
- On restore failures with NotFound, refresh the listing before surfacing an error to the user.
- Delete orphaned .trashinfo files on unmounted/removable roots so they stop producing ids.
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
- trash item is not a directory
- trash item has no put-back location
- invalid trash info path
- Tab with id {id} not found
- Unsupported OS for trash operation
AI-assisted analysis of sxyazi/yazi@94abcfa92f (2026-08-16).
Data as JSON: /api/errors/3b5af4e132fe37a9.
Report an issue: GitHub.