From e229763a86135ed560b0e6680f8bab76722a8463 Mon Sep 17 00:00:00 2001 From: John <93539122+CountMyBands@users.noreply.github.com> Date: Mon, 5 Oct 2026 10:24:47 -0400 Subject: [PATCH] Ignore watcher events for paths that no longer exist (AI-assisted) Package managers replace nested dependency trees non-atomically, so by the time a queued write, remove, or rescan event is processed, both the event path and its parent can already be gone. That is expected rather than an error, so log it at trace level instead of warning on every such event. Co-Authored-By: Claude Opus 5.5 (1M context) --- src/change_processor.rs | 47 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/change_processor.rs b/src/change_processor.rs index 1faa9928..193e8fa6 100644 --- a/src/change_processor.rs +++ b/src/change_processor.rs @@ -183,6 +183,13 @@ impl JobThreadContext { match self.vfs.canonicalize(parent) { Ok(parent) => self.apply_patches(parent.join(file_name)), + Err(err) if err.kind() == std::io::ErrorKind::NotFound => { + log::trace!( + "Ignoring stale filesystem event for missing path {}", + path.display() + ); + Vec::new() + } Err(err) => { log::warn!( "Could not canonicalize parent of filesystem event path {}: {}", @@ -205,6 +212,13 @@ impl JobThreadContext { match self.vfs.canonicalize(parent) { Ok(parent) => self.apply_patches(parent), + Err(err) if err.kind() == std::io::ErrorKind::NotFound => { + log::trace!( + "Ignoring filesystem rescan for missing path {}", + path.display() + ); + Vec::new() + } Err(err) => { log::warn!( "Could not canonicalize parent of filesystem rescan path {}: {}", @@ -434,3 +448,36 @@ fn compute_and_apply_changes(tree: &mut RojoTree, vfs: &Vfs, id: Ref) -> Option< Some(applied_patch_set) } + +#[cfg(test)] +mod test { + use super::*; + use memofs::StdBackend; + + use crate::snapshot::InstanceSnapshot; + + #[test] + fn missing_nested_watcher_paths_do_not_panic() { + let dir = tempfile::tempdir().unwrap(); + let missing_path = dir + .path() + .join("Packages") + .join("_Index") + .join("package@1.0.0") + .join("module.lua"); + + let context = JobThreadContext { + tree: Arc::new(Mutex::new(RojoTree::new( + InstanceSnapshot::new().class_name("Folder"), + ))), + vfs: Arc::new(Vfs::new(StdBackend::new().unwrap())), + message_queue: Arc::new(MessageQueue::new()), + }; + + // Package managers replace nested dependency trees non-atomically. By + // the time a queued write or remove event is processed, both the event + // path and its parent can already be gone. + context.handle_vfs_event(VfsEvent::Write(missing_path.clone())); + context.handle_vfs_event(VfsEvent::Remove(missing_path)); + } +}