{"record":{"id":"3b5af4e132fe37a9","repo":"sxyazi/yazi","slug":"trash-item-outside-of-trash-folders","errorCode":null,"errorMessage":"trash item outside of trash folders","messagePattern":"trash item outside of trash folders","errorType":"validation","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"yazi-fs/src/trash/freedesktop/trash.rs","lineNumber":40,"sourceCode":"\t\t\treturn Err(io::Error::new(io::ErrorKind::InvalidInput, \"trash item is not a directory\"));\n\t\t}\n\n\t\tfs::read_dir(&entry.backing)?\n\t\t\t.map(|dent| {\n\t\t\t\tlet dent = dent?;\n\t\t\t\tentry.child(dent.file_name())\n\t\t\t})\n\t\t\t.collect()\n\t}\n\n\tpub(crate) fn entry(&self, id: &TrashId) -> io::Result<TrashEntry> {\n\t\tlet info = TrashInfo::parse(id.top())?;\n\t\tif !os_limited::trash_folders()\n\t\t\t.map_err(io::Error::other)?\n\t\t\t.iter()\n\t\t\t.any(|folder| folder == &info.root)\n\t\t{\n\t\t\treturn Err(io::Error::new(io::ErrorKind::NotFound, \"trash item outside of trash folders\"));\n\t\t}\n\n\t\tif id.has_rel() && !fs::symlink_metadata(&info.backing)?.file_type().is_dir() {\n\t\t\treturn Err(io::Error::new(io::ErrorKind::InvalidInput, \"trash item is not a directory\"));\n\t\t}\n\n\t\tlet (backing, original) = if id.has_rel() {\n\t\t\t(info.backing.join(id.rel()), info.original.join(id.rel()))\n\t\t} else {\n\t\t\t(info.backing, info.original)\n\t\t};\n\t\tTrashEntry::new(id.clone(), backing, Some(original))\n\t}\n\n\tpub(crate) fn metadata(&self, entry: &TrashEntry, follow: bool) -> io::Result<Cha> {\n\t\tOk(if follow { entry.cha } else { entry.lcha })\n\t}\n","sourceCodeStart":22,"sourceCodeEnd":58,"githubUrl":"https://github.com/sxyazi/yazi/blob/94abcfa92f4ad3f0a1aef6c1ea083cfb8aa6c8c2/yazi-fs/src/trash/freedesktop/trash.rs#L22-L58","documentation":"`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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nlet entry = trash.entry(&cached_id)?; // NotFound after drive unmount\n\n// after\nmatch trash.entry(&cached_id) {\n    Ok(entry) => { /* proceed */ }\n    Err(e) if e.kind() == io::ErrorKind::NotFound => {\n        let fresh: Vec<_> = trash.list(None)?; // rebuild from current trash folders\n    }\n    Err(e) => return Err(e),\n}","handlingStrategy":"try-catch","validationCode":"use std::path::Path;\n\nfn in_known_trash_folder(info_path: &Path, folders: &[std::path::PathBuf]) -> bool {\n    // root = info_path.parent().filter(name==\"info\").and_then(parent)\n    info_path\n        .parent()\n        .filter(|p| p.file_name().map(|n| n == \"info\").unwrap_or(false))\n        .and_then(std::path::Path::parent)\n        .is_some_and(|root| folders.iter().any(|f| f == root))\n}","typeGuard":null,"tryCatchPattern":"match trash.entry(&id) {\n    Ok(e) => { /* ... */ }\n    Err(e) if e.kind() == io::ErrorKind::NotFound => {\n        // stale id: discard and re-list via trash.list(None)\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["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."],"tags":["trash","freedesktop","not-found","stale-id","removable-media"],"backgroundTag":null,"analyzedSha":"94abcfa92f4ad3f0a1aef6c1ea083cfb8aa6c8c2","analyzedAt":"2026-08-16T09:56:24.836Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}