jesseduffield / jesseduffield/lazygit
PTY exhaustion: unreaped delta→less pager processes accumulate on every diff render
- 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
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