gitbutlerapp/gitbutler · warning · anyhow::Error

Project at '{}' is already opened for writing by another Git

Error message

Project at '{}' is already opened for writing by another GitButler instance

What it means

`try_exclusive_inter_process_access(project_data, LockScope::AllOperations)` takes an advisory lock file (`project.lock`) in the project's data directory. If `try_lock()` reports it's already held, another live GitButler process owns the project for writing, and this bail fires. The lock is released on process exit for any reason, so it cannot go stale — the competing process is genuinely alive.

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. Close the other GitButler instance (quit the desktop app fully, including tray) and retry.
  2. Find lingering holders: `ps aux | grep -i gitbutler` (or check the process holding the lock with `lsof <project_data>/project.lock`) and exit it normally.
  3. If the holder is a stuck process, kill it — the OS then releases the advisory lock since it cannot go stale on its own.
  4. For CLI tools meant to run alongside the app, use scopes/APIs designed for coexistence instead of the full AllOperations lock.
Defensive patterns

Strategy: retry

Validate before calling

// Check whether the lock is currently held before attempting a full open
use std::fs::File;
use fs4::FileExt; // same family but-core uses for the advisory lock
fn project_lock_free(project_data: &std::path::Path) -> bool {
    File::open(project_data.join("project.lock"))
        .and_then(|f| f.try_lock_exclusive().map(|_| true))
        .unwrap_or(false)
}

Try / catch

// Treat as contention: prompt to close the other instance, then retry
for attempt in 0..3 {
    match try_exclusive_inter_process_access(&dir, LockScope::AllOperations) {
        Ok(lock) => return Ok(lock),
        Err(err) if err.to_string().contains("another GitButler instance") && attempt < 2 => {
            notify_user("Close the other GitButler window/app, then retrying…");
            std::thread::sleep(std::time::Duration::from_secs(2));
        }
        Err(err) => return Err(err),
    }
}

Prevention

When it happens

Trigger: Opening the same project in a second GitButler desktop instance, or running a GitButler CLI/TUI command against a project the desktop app currently holds; two `but` invocations racing on the same project data directory.

Common situations: Desktop app running in the tray while the user runs `but` in a terminal; a second app window/installation pointed at the same project directory; a hung previous instance that is still alive but invisible.

Related errors


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