gitbutlerapp/gitbutler · warning · anyhow::Error

Project at '{}' is already being refreshed in the background

Error message

Project at '{}' is already being refreshed in the background by another GitButler instance

What it means

Same `try_exclusive_inter_process_access()` path as the write lock, but with `LockScope::BackgroundRefreshOperations`, which locks `background-refresh.lock` instead of `project.lock`. It means another GitButler instance is currently running the background refresh for this project. User-driven operations may still proceed; only the background refresh refuses to overlap.

Source

Thrown at crates/but-core/src/sync.rs:68

    let got_lock = lock
        .try_lock()
        .context("Failed to check if lock is taken")?;
    if !got_lock {
        let error_message = match scope {
            LockScope::AllOperations => {
                format!(
                    "Project at '{}' is already opened for writing by another GitButler instance",
                    project_data.display()
                )
            }
            LockScope::BackgroundRefreshOperations => {
                format!(
                    "Project at '{}' is already being refreshed in the background by another GitButler instance",
                    project_data.display()
                )
            }
        };
        bail!(error_message);
    }
    Ok(lock)
}

/// Return a guard for exclusive (read+write) *in-process* repository access for the project at
/// `git_dir`, blocking while waiting for someone else in this process to release it, or for all
/// readers to disappear. Locking is fair.
/// Also use `project_data_dir` if `Some` to create an *inter-process* exclusive lock.
/// Creating, opening, or locking that file is best-effort. Failures are logged and ignored, so
/// the hard guarantee provided by this function remains in-process exclusivity only.
///
/// If the current process inherits Git's commit-hook environment (`GIT_EDITOR=:` together with
/// `GIT_INDEX_FILE`), acquiring the inter-process lock becomes non-blocking: if another process
/// already holds it, we continue without that file lock instead of waiting. This avoids
/// deadlocking hook re-entry when the parent GitButler command is already holding the same
/// inter-process lock while waiting for the hook to finish.
/// If `project_data_dir` is `None`, no inter-process lock is obtained.
///

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Treat as transient: the refresh lock is short-lived, so simply retry after the other instance's refresh completes.
  2. Ensure only one long-running GitButler instance per project data directory; exit extras.
  3. If it persists, identify the holder via `lsof <project_data>/background-refresh.lock` and let it finish or stop it.
  4. Schedule your refresh-triggering work to avoid racing the desktop app's refresh window.
Defensive patterns

Strategy: retry

Try / catch

// Background-refresh contention is transient: back off and retry
let mut delay = std::time::Duration::from_millis(500);
loop {
    match try_exclusive_inter_process_access(&dir, LockScope::BackgroundRefreshOperations) {
        Ok(lock) => break lock,
        Err(err) if err.to_string().contains("being refreshed in the background") => {
            std::thread::sleep(delay);
            delay = (delay * 2).min(std::time::Duration::from_secs(30));
        }
        Err(err) => return Err(err),
    }
}

Prevention

When it happens

Trigger: Two instances both scheduling background refreshes for the same project data directory — desktop app's periodic refresh while a second app instance or a long-lived daemon also refreshes; rapid restarts where the old process's refresh task has not finished.

Common situations: App restarted quickly while the previous process was mid-refresh; a GitButler service/agent and the interactive app pointed at one project.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20). Data as JSON: /api/errors/2411cc81db9d0a71. Report an issue: GitHub.