You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Lazygit's PTY pager stop path only kills the direct child, not the process group, which can leave zombie/orphan pager processes #5675
Lazygit delegates some git output to a pager (commonly less, but also any user-configured pager) via a PTY. When the user navigates away, lazygit closes the PTY master and waits for the direct child process to exit (jesseduffield/lazygit#4782). However, this assumes the pager correctly observes a closed controlling terminal and exits promptly. Not all pagers do.
An upstream bug in less (gwsw/less#793, PR gwsw/less#794) demonstrates that less can spin at 100% CPU instead of exiting when its tty fd returns EOF, because getchr() loops forever without handling read() == 0. Even if a fix is merged upstream, most Linux distributions will not ship an updated less package for months. More importantly, lazygit supports arbitrary user-configured pagers (core.pager, PAGER, or GIT_PAGER), and lazygit cannot rely on every pager being bug-free.
Today lazygit's stop path:
Calls TerminateProcessGracefully(cmd), which on non-Windows sends SIGTERM only to the direct cmd.Process.
Closes the PTY master (ptmx.Close()).
Waits for cmd.Wait() in a goroutine (or synchronously in some paths).
If the pager ignores SIGTERM and does not notice the closed PTY, it can survive as an orphan, consuming CPU and holding any LESSOPEN preprocessor children alive. For less specifically this is a known failure mode; for other pagers it is an unvalidated risk.
To Reproduce
Set core.pager=less (default on many systems) or any pager that may not exit cleanly on closed PTY.
Open a file/diff large enough to trigger the pager inside lazygit's panel.
Navigate away so lazygit closes the PTY and signals the direct child.
Observe (e.g. via ps) whether the pager process (and any LESSOPEN child processes) remain.
With current less versions affected by gwsw/less#793, the process may spin at 100% CPU.
Expected behavior
When lazygit closes a PTY, all processes in that PTY's session should be reliably terminated, regardless of whether the pager correctly handles a closed terminal. Lazygit should not depend on the pager being well-behaved.
Screenshots
N/A
Version info
Reproducible on current master and any recent release that uses PTY-based pagers.
Terminal info
Any terminal / OS where lazygit spawns pagers via PTY (not Windows, where PTY pager path is different).
Additional context
Lazygit already improved graceful termination in jesseduffield/lazygit#4782 to reduce stale index.lock. The remaining gap is that TerminateProcessGracefully only signals the direct child process, not the process group. When creack/pty's StartWithSize starts the process in a new session (Setsid and Setctty), the child's PID is also its process-group ID. Lazygit could send SIGHUP to the process group (kill(-cmd.Process.Pid, syscall.SIGHUP)), close the PTY, then wait with a short timeout (e.g. 500 ms), and finally SIGKILL the group if still alive. This should be limited to non-Windows platforms where process-group signals are meaningful.
This is a lazygit-side reliability bug: the stop path trusts the pager to exit, but that trust is not safe for arbitrary pagers or lagging distro packages.
Closing this: the repro steps don't reproduce — core.pager has no effect on lazygit (thanks @stefanhaller), so the pager-orphan path as described can't trigger. The WARNING symptom turned out to have a separate config-side root cause (TERM=dumb PTY + renderer-spawned less via delta paging; upstream #4837 removed the useConfig footgun). The termhang/pager-cleanup work stays on my fork as reference.
Activity
stefanhaller commentedon Jun 5, 2026
I'm confused. Did your AI agent run wild?
Lazygit doesn't support less as a pager, and setting
core.pager=lessdoesn't have any affect on lazygit.Can you reproduce an issue with any of the pagers that lazygit really supports?
HaleTom commentedon Jun 5, 2026
@stefanhaller Perhaps it did run wild -- I'll look into something reproducible. I think its a
- pager:config.HaleTom commentedon Jun 10, 2026
@stefanhaller please see this for three tests that demonstrate the issue:
https://github.com/jesseduffield/lazygit/compare/master...HaleTom:lazygit:bug-demo?expand=1
HaleTom commentedon Jun 10, 2026
I'm cooking a PR (in progress on my own fork), but took a side track to look at the less issue first.
HaleTom commentedon Jun 20, 2026
@stefanhaller I've been through quite the review process with the sadly soon to be retired google review bot.
Can you please take a look at:
HaleTom#3
Thanks!
Give task commands a Terminate method instead of exposing their process
Give task commands a Terminate method instead of exposing their process
Give task commands a Terminate method instead of exposing their process
Give task commands a Terminate method instead of exposing their process
Give task commands a Terminate method instead of exposing their process
Give task commands a Terminate method instead of exposing their process
HaleTom commentedon Oct 22, 2026
Closing this: the repro steps don't reproduce — core.pager has no effect on lazygit (thanks @stefanhaller), so the pager-orphan path as described can't trigger. The WARNING symptom turned out to have a separate config-side root cause (TERM=dumb PTY + renderer-spawned less via delta paging; upstream #4837 removed the useConfig footgun). The termhang/pager-cleanup work stays on my fork as reference.