{"record":{"id":"145c16b05e2f2f6b","repo":"gitbutlerapp/gitbutler","slug":"file-monitor-stopped","errorCode":null,"errorMessage":"file monitor stopped","messagePattern":"file monitor stopped","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/gitbutler-filemonitor/src/file_monitor.rs","lineNumber":62,"sourceCode":"const FLUSH_AFTER_EMPTY: u32 = 3;\n\nenum Command {\n    Flush,\n}\n\n/// Handle for a running file monitor spawned with [`spawn()`].\n///\n/// Dropping this handle will stop the monitor as soon as it tries to send the next event, failing as there is no receiver.\npub struct FileMonitorHandle {\n    cmd_tx: std::sync::mpsc::Sender<Command>,\n}\n\nimpl FileMonitorHandle {\n    /// Request that pending filesystem events are emitted immediately.\n    pub fn flush(&self) -> Result<()> {\n        self.cmd_tx\n            .send(Command::Flush)\n            .map_err(|_| anyhow!(\"file monitor stopped\"))\n    }\n}\n\nconst ENV_WATCH_MODE: &str = \"GITBUTLER_WATCH_MODE\";\n\n/// Control how the filesystem watch should be established.\n#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]\npub enum WatchMode {\n    /// Recursively watch the worktree (and an extra git-dir if the repo uses\n    /// a linked worktree with a git-dir outside the worktree), using [`notify::RecursiveMode::Recursive`].\n    Legacy,\n    /// Ignore-aware watch plan: non-recursive watches of non-ignored worktree directories,\n    /// plus explicit git-dir watches and dynamic watch additions for newly created directories.\n    /// Each directory is watched with [`notify::RecursiveMode::NonRecursive`].\n    Modern,\n    /// Automatically pick a mode based on platform heuristics.\n    ///\n    #[default]","sourceCodeStart":44,"sourceCodeEnd":80,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/58e5313667b857ef39a730e380af31816a7b1768/crates/gitbutler-filemonitor/src/file_monitor.rs#L44-L80","documentation":"FileMonitorHandle::flush sends a Flush command over a channel to the monitor worker thread. If the send fails, the receiver has been dropped, meaning the monitor thread has exited/shut down and can no longer emit pending events. The error is a channel-closed surrogate for 'monitor is dead'.","triggerScenarios":"Calling flush() after the file monitor was stopped or its worker thread panicked/exited; flushing during app shutdown after cmd_tx's receiver was dropped.","commonSituations":"Calling flush on a handle whose monitor was dropped; race between shutdown and a UI/editor action requesting a flush; watch teardown due to a fatal watch error earlier.","solutions":["Check whether the monitor was shut down before calling flush; keep the monitor alive for the handle's lifetime.","Treat this error as benign during shutdown and ignore/log it.","Recreate the file monitor if continued watching is needed.","Check logs for an earlier monitor-thread panic (e.g. watch setup failure) that caused the exit."],"exampleFix":"// before\nhandle.flush()?; // Err even if you just want pending events\n// after\nif let Err(e) = handle.flush() {\n    tracing::warn!(\"file monitor unavailable: {e}\"); // benign at shutdown\n}","handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"match handle.flush() {\n    Ok(()) => {},\n    Err(e) => log::warn!(\"file monitor stopped, skipping flush: {e}\"), // benign during shutdown\n}","preventionTips":["Keep the monitor instance alive as long as its handle is used.","Order shutdown: drop handles before stopping the monitor.","Log and ignore flush errors during teardown rather than propagating."],"tags":["file-monitor","channel-closed","shutdown"],"backgroundTag":"thread-interrupted","analyzedSha":"58e5313667b857ef39a730e380af31816a7b1768","analyzedAt":"2026-09-18T06:50:32.052Z","contentChangedAt":"2026-09-18T06:50:32.052Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}