# How Lazygit Handles Interactive Rebase Operations Under the Hood

> Discover how Lazygit manages interactive rebase operations by editing git-rebase-todo files with a daemon process and UI commands.

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

---

**Lazygit handles interactive rebase operations by acting as its own Git sequence editor, using a daemon process that programmatically edits the git-rebase-todo file based on UI commands.**

Lazygit, the terminal UI for Git operations in the jesseduffield/lazygit repository, automates complex Git workflows by embedding itself into Git's command pipeline. When users initiate an interactive rebase through the TUI, lazygit leverages a sophisticated daemon pattern to manipulate the rebase todo list without requiring manual file editing.

## The Architecture of Lazygit's Interactive Rebase System

### UI Entry Points in LocalCommitsController

The rebase flow begins in [`pkg/gui/controllers/local_commits_controller.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/gui/controllers/local_commits_controller.go), where the `LocalCommitsController` captures user keypresses. Methods like `quickStartInteractiveRebase` and `startInteractiveRebaseWithEdit` translate UI actions into calls to the rebase command layer, passing commit selections and desired operations (pick, squash, reword, drop) as structured data.

### Command Construction in RebaseCommands

The `RebaseCommands` struct in [`pkg/commands/git_commands/rebase.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/commands/git_commands/rebase.go) constructs the actual Git command through `PrepareInteractiveRebaseCommand`. This function builds a `git rebase` invocation with flags `--interactive`, `--autostash`, `--keep-empty`, and `--rebase-merges`, targeting the appropriate base commit.

Crucially, it injects environment variables that transform lazygit from a TUI into a sequence editor:

- `GIT_SEQUENCE_EDITOR=<path-to-lazygit>` tells Git to invoke lazygit for editing the todo file.
- `LAZYGIT_DAEMON_KIND` encodes the operation type (e.g., ChangeTodoActions, InsertBreak).
- `LAZYGIT_DAEMON_INSTRUCTION` carries a JSON-encoded payload with specific parameters.

The command object is returned to the caller and executed via `Run`.

### The Daemon Pattern

When Git spawns lazygit as the sequence editor, the binary detects daemon mode via `daemon.InDaemonMode()` in [`pkg/app/daemon/daemon.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/app/daemon/daemon.go). Instead of launching the TUI, control passes to `daemon.Handle`, which deserializes the instruction, routes it to the appropriate handler, and executes the file manipulation before exiting cleanly. This allows Git to proceed with the rebase using the programmatically modified todo list.

## Step-by-Step Execution Flow

The complete interactive rebase operation follows six distinct phases:

1. **User Initiation** – The user selects commits in the lazygit TUI and triggers an interactive rebase action (e.g., changing a commit to "squash").

2. **Instruction Serialization** – The controller creates a specific instruction object (e.g., `ChangeTodoActionsInstruction`) and serializes it to JSON along with the daemon kind identifier.

3. **Environment Setup** – `PrepareInteractiveRebaseCommand` sets `GIT_SEQUENCE_EDITOR` to the lazygit binary path and injects `LAZYGIT_DAEMON_KIND` and `LAZYGIT_DAEMON_INSTRUCTION` into the environment.

4. **Git Invocation** – The command executes `git rebase --interactive <base>`, causing Git to pause and invoke lazygit as the sequence editor.

5. **Daemon Execution** – Lazygit detects daemon mode, parses the instruction, and calls utility functions in [`pkg/utils/rebase_todo.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/utils/rebase_todo.go) (such as `EditRebaseTodo`, `MoveTodosUp`, or `InsertBreak`) to modify the `git-rebase-todo` file in place.

6. **Rebase Continuation** – The daemon exits with status 0, Git reads the modified todo file, and continues the rebase automatically, applying picks, squashes, rewords, or drops as specified.

## Key Source Files and Implementation Details

Understanding the implementation requires familiarity with these critical files in the jesseduffield/lazygit repository:

- **[`pkg/gui/controllers/local_commits_controller.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/gui/controllers/local_commits_controller.go)** – Entry point for user interactions. Contains `quickStartInteractiveRebase` and `startInteractiveRebaseWithEdit` methods that bridge UI events to the rebase command layer.

- **[`pkg/commands/git_commands/rebase.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/commands/git_commands/rebase.go)** – Core rebase logic. The `PrepareInteractiveRebaseCommand` function constructs the Git command and configures the daemon environment variables (`GIT_SEQUENCE_EDITOR`, `LAZYGIT_DAEMON_KIND`, `LAZYGIT_DAEMON_INSTRUCTION`).

- **[`pkg/app/daemon/daemon.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/app/daemon/daemon.go)** – Daemon detection and dispatch. Implements `InDaemonMode()` to check for the presence of daemon environment variables and `Handle()` to route instructions to their handlers.

- **[`pkg/app/daemon/rebase.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/app/daemon/rebase.go)** – Rebase-specific daemon operations. Defines instruction types like `ChangeTodoActionsInstruction`, `InsertBreakInstruction`, and `WriteRebaseTodoInstruction`, each implementing the `run` method that manipulates the todo file.

- **[`pkg/utils/rebase_todo.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/utils/rebase_todo.go)** – Low-level file manipulation. Provides `EditRebaseTodo` for changing actions, `MoveTodosUp`/`MoveTodosDown` for reordering, and `PrependStrToTodoFile` for inserting breaks. These functions leverage the `github.com/stefanhaller/git-todo-parser` library for robust parsing.

## Practical Code Examples

The following examples illustrate how lazygit programmatically initiates and manipulates interactive rebases:

**Initiating an interactive rebase on the current branch:**

```go
// From pkg/gui/controllers/local_commits_controller.go
c.c.Git().Rebase.BeginInteractiveRebaseForCommit(
    c.c.Model().Commits,
    selectedIdx,
    false,
)

```

**Preparing the command with daemon environment variables:**

```go
// From pkg/commands/git_commands/rebase.go
cmdObj := self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
    baseHashOrRoot: baseHash,
    instruction:    daemon.NewChangeTodoActionsInstruction(changes),
    overrideEditor: true, // Use lazygit as the editor
})
// Execute the command, which invokes git rebase --interactive
cmdObj.Run()

```

**Changing commit actions to squash:**

```go
changes := []daemon.ChangeTodoAction{
    {Hash: "a1b2c3d", NewAction: todo.Squash, Flag: ""},
    {Hash: "d4e5f6g", NewAction: todo.Squash, Flag: ""},
}
c.c.Git().Rebase.InteractiveRebase(commits, startIdx, endIdx, todo.Squash, "")

```

**Inserting a break line to pause the rebase:**

```go
// Creates an InsertBreakInstruction internally
c.c.Git().Rebase.EditRebase(branchRef)

```

**Moving commits down in the todo list:**

```go
c.c.Git().Rebase.MoveCommitsDown(commits, startIdx, endIdx)

```

**Removing update-ref entries for copied branches:**

```go
c.c.Git().Rebase.DeleteUpdateRefTodos(commits)

```

## Summary

- **Lazygit acts as its own sequence editor** by setting `GIT_SEQUENCE_EDITOR` to its own binary path, allowing it to intercept and modify the `git-rebase-todo` file programmatically.
- **Daemon mode detection** occurs via environment variables (`LAZYGIT_DAEMON_KIND` and `LAZYGIT_DAEMON_INSTRUCTION`), triggering a non-interactive file manipulation mode instead of the TUI.
- **Instruction-based architecture** serializes UI actions (squash, reword, move, break) into JSON instructions that the daemon deserializes and executes against the todo file.
- **Robust file manipulation** is handled by utilities in [`pkg/utils/rebase_todo.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/utils/rebase_todo.go), which leverage the `git-todo-parser` library to correctly handle merge commits, update-ref entries, and comment lines.
- **Seamless Git integration** means that after the daemon exits, Git continues the rebase automatically using the modified todo list, requiring no manual text editing from the user.

## Frequently Asked Questions

### How does lazygit avoid opening a text editor during interactive rebase?

Lazygit sets the `GIT_SEQUENCE_EDITOR` environment variable to point to its own binary. When Git starts an interactive rebase, it invokes lazygit as the "editor" to modify the `git-rebase-todo` file. Lazygit detects it is running in daemon mode via the `LAZYGIT_DAEMON_KIND` environment variable, programmatically edits the file according to the serialized instruction, and exits immediately—allowing Git to continue without ever launching an interactive text editor.

### What happens if the rebase todo file contains merge commits or update-ref entries?

The lazygit daemon uses the `github.com/stefanhaller/git-todo-parser` library through utility functions in [`pkg/utils/rebase_todo.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/utils/rebase_todo.go). These utilities correctly parse and preserve complex todo file structures including merge commit lines (`label`, `reset`, `merge`) and `update-ref` entries. Specific instructions like `RemoveUpdateRefsForCopiedBranch` can selectively remove update-ref lines when rebasing copied branches, while maintaining the integrity of the remaining todo list.

### Can lazygit perform other rebase operations besides changing commit actions?

Yes, lazygit supports a comprehensive set of interactive rebase operations beyond simply changing actions (pick, squash, reword, drop). The daemon implements distinct instruction types including `InsertBreakInstruction` to pause rebases, `MoveTodosUp` and `MoveTodosDown` for reordering commits, and `WriteRebaseTodoInstruction` for wholesale file replacement. These are triggered through the `LocalCommitsController` and serialized via `LAZYGIT_DAEMON_INSTRUCTION` for the daemon to execute.

### Where is the rebase command actually constructed in the lazygit source code?

The core command construction occurs in [`pkg/commands/git_commands/rebase.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/commands/git_commands/rebase.go) within the `PrepareInteractiveRebaseCommand` function. This function assembles the `git rebase` command with flags like `--interactive`, `--autostash`, and `--keep-empty`, sets the `GIT_SEQUENCE_EDITOR` environment variable to the lazygit binary path, and injects the daemon instruction variables (`LAZYGIT_DAEMON_KIND` and `LAZYGIT_DAEMON_INSTRUCTION`) before returning the command object for execution.