Skip to content

Lazygit's PTY pager stop path only kills the direct child, not the process group, which can leave zombie/orphan pager processes #5675

Closed

Description

@HaleTom

Describe the bug

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:

  1. Calls TerminateProcessGracefully(cmd), which on non-Windows sends SIGTERM only to the direct cmd.Process.
  2. Closes the PTY master (ptmx.Close()).
  3. 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

  1. Set core.pager=less (default on many systems) or any pager that may not exit cleanly on closed PTY.
  2. Open a file/diff large enough to trigger the pager inside lazygit's panel.
  3. Navigate away so lazygit closes the PTY and signals the direct child.
  4. 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.

Activity

  1. stefanhaller commented on Jun 5, 2026

    @stefanhaller
    Collaborator

    I'm confused. Did your AI agent run wild?

    Lazygit doesn't support less as a pager, and setting core.pager=less doesn't have any affect on lazygit.

    Can you reproduce an issue with any of the pagers that lazygit really supports?

  2. HaleTom commented on Jun 5, 2026

    @HaleTom
    Author

    @stefanhaller Perhaps it did run wild -- I'll look into something reproducible. I think its a - pager: config.

  3. HaleTom commented on Jun 10, 2026

    @HaleTom
    Author
  4. HaleTom commented on Jun 10, 2026

    @HaleTom
    Author

    I'm cooking a PR (in progress on my own fork), but took a side track to look at the less issue first.

  5. HaleTom commented on Jun 20, 2026

    @HaleTom
    Author

    @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!

  6. HaleTom commented on Oct 22, 2026

    @HaleTom
    Author

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions