# How LazyGit Handles Concurrent Git Operations and Prevents Race Conditions

> Discover how LazyGit prevents race conditions during concurrent Git operations using mutexes and robust deadlock detection. Learn about its sophisticated concurrency control.

- Repository: [Jesse Duffield/lazygit](https://github.com/jesseduffield/lazygit)
- Tags: internals
- Published: 2026-03-02

---

**LazyGit prevents race conditions by layering command-level mutexes, context-level locks, and global GUI state mutexes, all backed by the `go-deadlock` library for detection during development.**

LazyGit executes multiple Git commands simultaneously—from background fetches to interactive rebases—while keeping the UI responsive. To ensure repository integrity and prevent data races, the `jesseduffield/lazygit` codebase implements a sophisticated locking strategy that spans individual commands, UI contexts, and global application state.

## Command-Level Mutexes for Git Processes

At the lowest layer, LazyGit guarantees that a single Git process cannot start twice at the same time (e.g., two overlapping `git fetch` operations). Each `CmdObj` defined in [`pkg/commands/oscommands/cmd_obj.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/commands/oscommands/cmd_obj.go) carries an optional `*deadlock.Mutex`. The `cmdObjRunner` in [`pkg/commands/oscommands/cmd_obj_runner.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/commands/oscommands/cmd_obj_runner.go) checks `cmdObj.Mutex()` and locks it before executing the command, unlocking only after the process exits.

This mechanism is critical for long-running operations. When a controller initiates a fetch, it attaches the GUI-wide `SubprocessMutex` to the command object:

```go
// In a controller method (e.g., *fetch* action)
func (self *FetchController) FetchAll() error {
    // Use the GUI‑wide subprocess mutex to serialize fetches
    gitCmd := self.c.Git().
        GitCommand("fetch", "--all").
        WithMutex(&self.c.SubprocessMutex)

    // Execute the command; the runner will lock the mutex automatically
    return gitCmd.Run()
}

```

While the lock is held, any other command attempting to use the same mutex blocks, avoiding simultaneous `git fetch` or `git rebase` operations that could corrupt the repository state.

## Context-Level Mutexes for UI Safety

To prevent overlapping updates to UI contexts—such as the file list, branch list, or stash list—every GUI context implements `GetMutex() *deadlock.Mutex`. Controllers lock the context mutex around any read-modify-write sequence, ensuring that a background refresh cannot mutate a slice while the UI thread iterates over it.

For example, [`pkg/gui/context/merge_conflicts_context.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/gui/context/merge_conflicts_context.go) and [`pkg/gui/context/patch_explorer_context.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/gui/context/patch_explorer_context.go) both expose this interface. A typical refresh pattern looks like this:

```go
func (self *FileListController) RefreshFiles() {
    // Acquire the file‑list context mutex
    ctx := self.c.Contexts().Files
    ctx.GetMutex().Lock()
    defer ctx.GetMutex().Unlock()

    // Safe to read/write the file slice here
    self.c.State().Files = self.c.Git().GetStatus()
}

```

This pattern prevents data races between the main thread rendering the UI and background goroutines updating the underlying Git state.

## Global GUI State Mutexes for Coarse-Grained Operations

For expensive operations that touch many parts of the UI—such as refreshing all panels, loading commits, or running a rebase—LazyGit uses deliberately coarse-grained mutexes. The `guiCommon` struct in [`pkg/gui/types/common.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/gui/types/common.go) contains fields like `RefreshingFilesMutex`, `SubprocessMutex`, and `PopupMutex`. These serialize high-level operations to avoid dead-interleaving of UI updates.

When entering a rebase or opening a popup, controllers lock these higher-level mutexes to guarantee that only one modal operation can alter the UI or Git process at a time.

## Background Routine Management

LazyGit runs periodic `git fetch` operations in the background without colliding with manual fetches. The `BackgroundRoutineMgr` in [`pkg/gui/background.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/gui/background.go) manages this via the `backgroundFetch()` method, which creates a `CmdObj` with a dedicated reference to `self.c.SubprocessMutex`.

All background fetches obtain this lock, guaranteeing that a manual fetch always waits for a running background fetch to finish (or is skipped if a fetch is already in progress):

```go
func (m *BackgroundRoutineMgr) backgroundFetch() error {
    // Ensure only one background fetch runs at once
    cmd := m.c.Git().
        GitCommand("fetch", "--all").
        WithMutex(&m.c.SubprocessMutex)

    return cmd.Run()
}

```

## Cached Git Config Mutex

To protect in-memory caching of Git config values that may be read from disk by multiple goroutines, [`pkg/commands/git_config/cached_git_config.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/commands/git_config/cached_git_config.go) uses a standard `sync.Mutex` to guard the cache map. This ensures thread-safe access to configuration values without blocking the main Git command mutexes.

## Deadlock Detection in Development

During development, LazyGit uses the `github.com/sasha-s/go-deadlock` library (vendored under [`vendor/github.com/sasha-s/go-deadlock/deadlock.go`](https://github.com/jesseduffield/lazygit/blob/main/vendor/github.com/sasha-s/go-deadlock/deadlock.go)) to wrap every mutex. All mutexes throughout the codebase are `deadlock.Mutex` or `deadlock.RWMutex`. The library tracks lock ordering and panics if a potential deadlock is detected, alerting developers to synchronization errors before they reach users.

## Summary

- **Command-level mutexes** prevent simultaneous execution of conflicting Git commands by locking `CmdObj` instances in [`cmd_obj_runner.go`](https://github.com/jesseduffield/lazygit/blob/main/cmd_obj_runner.go).
- **Context-level mutexes** protect individual UI views (files, branches, stashes) from race conditions during background refreshes.
- **Global GUI state mutexes** serialize expensive, multi-panel operations like rebases and full refreshes.
- **BackgroundRoutineMgr** reuses the `SubprocessMutex` to coordinate automatic and manual `git fetch` operations.
- **Deadlock detection** via `go-deadlock` ensures the locking hierarchy remains safe during development.

## Frequently Asked Questions

### What mutex library does LazyGit use to detect deadlocks?

LazyGit uses `github.com/sasha-s/go-deadlock`, a drop-in replacement for `sync.Mutex` that tracks lock ordering and panics if it detects a potential deadlock. Every mutex in the codebase is a `deadlock.Mutex` or `deadlock.RWMutex`, allowing the test suite to catch synchronization errors early.

### How does LazyGit prevent simultaneous git fetch operations?

Both manual fetches (triggered by user input) and automatic background fetches acquire the `SubprocessMutex` defined in [`pkg/gui/types/common.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/gui/types/common.go). The `BackgroundRoutineMgr` in [`pkg/gui/background.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/gui/background.go) creates fetch commands using `WithMutex(&self.c.SubprocessMutex)`, ensuring only one fetch runs at a time.

### Why does LazyGit use both command-level and context-level mutexes?

Command-level mutexes protect the external Git process state (preventing conflicting commands like concurrent rebases), while context-level mutexes protect internal UI data structures (preventing slices from being modified while being rendered). This separation allows the UI to remain responsive while heavy Git operations execute.

### Where are the global GUI mutexes defined in the LazyGit codebase?

The global mutexes—such as `RefreshingFilesMutex`, `SubprocessMutex`, and `PopupMutex`—are defined as fields on the `guiCommon` struct in [`pkg/gui/types/common.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/gui/types/common.go). Controllers access these via the common interface to coordinate coarse-grained operations across the application.