{"record":{"id":"2411cc81db9d0a71","repo":"gitbutlerapp/gitbutler","slug":"project-at-is-already-being-refreshed-in-the","errorCode":null,"errorMessage":"Project at '{}' is already being refreshed in the background by another GitButler instance","messagePattern":"Project at '(.+?)' is already being refreshed in the background by another GitButler instance","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"warning","filePath":"crates/but-core/src/sync.rs","lineNumber":68,"sourceCode":"    let got_lock = lock\n        .try_lock()\n        .context(\"Failed to check if lock is taken\")?;\n    if !got_lock {\n        let error_message = match scope {\n            LockScope::AllOperations => {\n                format!(\n                    \"Project at '{}' is already opened for writing by another GitButler instance\",\n                    project_data.display()\n                )\n            }\n            LockScope::BackgroundRefreshOperations => {\n                format!(\n                    \"Project at '{}' is already being refreshed in the background by another GitButler instance\",\n                    project_data.display()\n                )\n            }\n        };\n        bail!(error_message);\n    }\n    Ok(lock)\n}\n\n/// Return a guard for exclusive (read+write) *in-process* repository access for the project at\n/// `git_dir`, blocking while waiting for someone else in this process to release it, or for all\n/// readers to disappear. Locking is fair.\n/// Also use `project_data_dir` if `Some` to create an *inter-process* exclusive lock.\n/// Creating, opening, or locking that file is best-effort. Failures are logged and ignored, so\n/// the hard guarantee provided by this function remains in-process exclusivity only.\n///\n/// If the current process inherits Git's commit-hook environment (`GIT_EDITOR=:` together with\n/// `GIT_INDEX_FILE`), acquiring the inter-process lock becomes non-blocking: if another process\n/// already holds it, we continue without that file lock instead of waiting. This avoids\n/// deadlocking hook re-entry when the parent GitButler command is already holding the same\n/// inter-process lock while waiting for the hook to finish.\n/// If `project_data_dir` is `None`, no inter-process lock is obtained.\n///","sourceCodeStart":50,"sourceCodeEnd":86,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-core/src/sync.rs#L50-L86","documentation":"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.","triggerScenarios":"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.","commonSituations":"App restarted quickly while the previous process was mid-refresh; a GitButler service/agent and the interactive app pointed at one project.","solutions":["Treat as transient: the refresh lock is short-lived, so simply retry after the other instance's refresh completes.","Ensure only one long-running GitButler instance per project data directory; exit extras.","If it persists, identify the holder via `lsof <project_data>/background-refresh.lock` and let it finish or stop it.","Schedule your refresh-triggering work to avoid racing the desktop app's refresh window."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Background-refresh contention is transient: back off and retry\nlet mut delay = std::time::Duration::from_millis(500);\nloop {\n    match try_exclusive_inter_process_access(&dir, LockScope::BackgroundRefreshOperations) {\n        Ok(lock) => break lock,\n        Err(err) if err.to_string().contains(\"being refreshed in the background\") => {\n            std::thread::sleep(delay);\n            delay = (delay * 2).min(std::time::Duration::from_secs(30));\n        }\n        Err(err) => return Err(err),\n    }\n}","preventionTips":["Jitter background-refresh schedules so multiple instances don't collide on the same cadence.","Keep only one long-lived GitButler process per project; secondary CLIs should skip refresh if the lock is taken.","Log at info (not error) when this lock is contended — it's expected coexistence behavior."],"tags":["locking","concurrency","background-refresh","gitbutler"],"backgroundTag":"file-lock-contention","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}