jesseduffield / jesseduffield/lazygit

PTY exhaustion: unreaped delta→less pager processes accumulate on every diff render

Open
#5,988 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Go
Stars
82.4k
Forks
3k
Avg merge
2d 18h
Merged PRs (30d)
19

Description

### Describe the bug

Running lazygit with a diff renderer that auto-pages (delta without `--paging=never`) leaks one blocked `git → delta → less` process chain per diff render, indefinitely. Each chain sits in a PTY waiting for input at less's `:` prompt. Over time this exhausted all 4096 kernel PTYs on our server (`/proc/sys/kernel/pty/nr` reached 4072), and new SSH logins failed with "unable to allocate pty". Killing the lazygit process released all ~4000 PTYs at once.

### To Reproduce

1. Configure a diff renderer that auto-pages:

```yaml
git:
diffRenderers:
- colorArg: always
command: delta --line-numbers
```

2. Open lazygit in a repo where a diff is taller than one screen (e.g. a large file in the staging view).
3. Refresh the view a few times (make changes, switch files, trigger refreshes) — each render starts a new `git diff --color=always | delta --line-numbers` chain; delta sees a TTY and starts `less`, which blocks waiting for a keypress in lazygit's background PTY.
4. Watch the process count / PTY usage grow with every render; nothing ever reaps the old chains.

The blocking behavior of delta itself can be seen outside lazygit (lazygit runs the renderer in a PTY, so the same thing happens there):

```sh
script -qec 'seq 1 200 | delta --line-numbers' /dev/null # blocks at less's ':' prompt
script -qec 'seq 1 200 | delta --line-numbers --paging=never' /dev/null # exits immediately
```

### Expected behavior

- When lazygit starts a new diff render, it should kill/cancel the previous renderer process, so a blocking pager can never accumulate more than one.
- Arguably it should also not hand the renderer a TTY that invites auto-paging (or at least warn when the configured renderer is delta without `--paging=never`, matching the docs' recommendation).

### Version info:

```
commit=fbe2379fa5831b1ce1a8a836a604652ffc14844f, build date=2026-08-12T17:52:51Z, build source=binaryRelease, version=0.64.1, os=linux, arch=amd64, git version=2.43.0
```

delta 0.19.2, less 590 (GNU regular expressions), Linux 6.8.0-106-generic

### Terminal info:

SSH into an Ubuntu server, GNOME Terminal locally. The leak also accumulated while sessions were completely idle/detached, so the terminal choice shouldn't matter.

### Additional context

- Global git config has `core.pager=delta`; the `diffRenderers` command above is what lazygit actually executed (`git diff --color=always | delta --line-numbers`).
- Evidence from the incident: `ps` showed ~4072 chains of `git diff` → `delta` → `less`, all blocked on the less prompt inside PTYs; `kernel.pty.nr` hit 4072/4096 and the server refused new SSH sessions until the lazygit process was killed.
- Workaround that fixed it for us: add `--paging=never` to the renderer command.
- I'm aware the docs recommend `--paging=never` for delta; the bug here is that forgetting it leads to an unbounded process/PTY leak rather than just a hung view.

Contributor guide

Open the contributing guide

Research direction

Start at the diff renderer process lifecycle and trace how a new render is launched when the view refreshes. Reproduce with the supplied delta command and a large diff, then verify that replacing or cancelling a render terminates and reaps the prior git, delta, and less processes instead of accumulating PTYs.

Written by the indexing model from the issue text.

Assessment

Tech stack
git, go
Domain
cli
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.