gitbutlerapp/gitbutler · error

not found

Error message

{} not found

What it means

Converts a notify (filesystem watcher) error into a backoff error. notify::ErrorKind::PathNotFound becomes a permanent backoff error with the message '<path> not found', so retries are skipped. Other IO/invalid-config errors are also permanent; everything else is transient.

Solutions

  1. Verify the watched path exists before setting up the watch (fs::metadata).
  2. Re-point the watch to the current valid project directory.
  3. If the path is expected to appear later, defer watch setup until it exists (this error is permanent and will not retry).
  4. Check whether a git operation (checkout/clean) removed the directory and re-establish the watch afterward.

Example fix

// before
watcher.watch(&project_path, Recursive)?;
// after
if project_path.exists() {
    watcher.watch(&project_path, Recursive)?;
}
Defensive patterns

Strategy: validation

Validate before calling

if !path.is_dir() {
    return Err(anyhow!("cannot watch missing directory: {}", path.display()));
}
// now safe to set up the watch

Prevention

When it happens

Trigger: Setting up a watch (setup_watch_plan / setup_legacy_watch) on a directory that does not exist or was deleted before/while watching — e.g. the workspace/project directory moved or removed.

Common situations: Project folder deleted or renamed externally while the app runs; watching a nested path removed during a git operation (checkout/clean); passing a stale path after re-cloning.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18). Data as JSON: /api/errors/e2577313ab8e8641. Report an issue: GitHub.

Appendix: source

Thrown at crates/gitbutler-filemonitor/src/file_monitor.rs:579

            )
    )
}

#[cfg(target_os = "windows")]
fn is_interesting_kind(kind: notify::EventKind) -> bool {
    matches!(
        kind,
        notify::EventKind::Create(_) | notify::EventKind::Modify(_) | notify::EventKind::Remove(_)
    )
}

fn into_backoff_err(
    err: notify::Error,
    path: &Path,
) -> backoff::Error<Box<dyn std::error::Error + Send + Sync + 'static>> {
    match err.kind {
        notify::ErrorKind::PathNotFound => backoff::Error::permanent(
            anyhow!("{} not found", path.display()).into_boxed_dyn_error(),
        ),
        notify::ErrorKind::Io(_) | notify::ErrorKind::InvalidConfig(_) => {
            backoff::Error::permanent(anyhow::Error::from(err).into_boxed_dyn_error())
        }
        _ => backoff::Error::transient(anyhow::Error::from(err).into_boxed_dyn_error()),
    }
}

fn backoff_err_to_anyhow(
    err: backoff::Error<Box<dyn std::error::Error + Send + Sync + 'static>>,
) -> anyhow::Error {
    anyhow::Error::from_boxed(Box::from(err.to_string()))
}

pub const LOCAL_REFS_DIR: &str = "refs/heads/";
pub const REMOTE_REFS_DIR: &str = "refs/remotes/";
pub const FETCH_HEAD: &str = "FETCH_HEAD";
pub const HEAD: &str = "HEAD";

View on GitHub (pinned to 58e5313667)