Skip to content

fix(ui): exit when the terminal hosting a review disconnects - #724

Open
NiqhtFire wants to merge 8 commits into
modem-dev:mainfrom
NiqhtFire:fix/terminal-disconnect-exit
Open

fix(ui): exit when the terminal hosting a review disconnects#724
NiqhtFire wants to merge 8 commits into
modem-dev:mainfrom
NiqhtFire:fix/terminal-disconnect-exit

Conversation

@NiqhtFire

Copy link
Copy Markdown

Problem

Hunk can keep running after the terminal hosting a review disappears, leaving behind a process that can no longer be reached.

I reproduced this in three cases:

  • When Hunk receives SIGHUP with session brokering enabled (the default), the renderer shuts down but the process stays alive indefinitely.
  • When the TTY is revoked, the process stays alive indefinitely. I stopped it manually after 30 seconds.
  • When the host closes the PTY master without killing the child, the process has a short CPU spike and exits roughly five seconds later.

Why this happens

The SIGHUP behavior comes from anomalyco/opentui#1355. OpenTUI's CliRenderer installs listeners for its default exitSignals, including SIGHUP. Its shared handler calls destroy(), but does not exit the process or restore the signal's default termination behavior. The process therefore exits only if nothing else is keeping the event loop alive.

Hunk's session broker client keeps the event loop alive, which is why the problem appears with the default configuration:

Configuration Result after kill -HUP
Brokering disabled (HUNK_MCP_DISABLE=1) Exits with status 0 in under a second because the event loop drains
Brokering enabled (default) Renderer is destroyed, but the process is still alive after 10 seconds

The pinned @opentui/core@0.4.3 uses the same exitHandler described in the issue. Handling SIGHUP in Hunk is the recommended workaround and can be removed once OpenTUI fixes the behavior upstream.

The upstream issue reports an orphaned process using roughly 100% CPU. However, Hunk behaves differently: after SIGHUP, the orphaned process idles at around 0.1–0.4% CPU, with a ~1s spike when the TTY is revoked. The main problem here is the stranded process not the CPU usage.

What changed

  • Shut down the session when the renderer's terminal input emits close, end, or error.
  • Shut down the session on SIGHUP.
  • Only watch for input disconnection when stdin is a TTY.

The TTY guard matters because non-terminal stdin, such as a pipe or /dev/null, ends as soon as the renderer resumes it. Without the guard, hunk diff a b < /dev/null exits successfully without rendering anything. test/cli/non-interactive-stdin.test.ts is written for this case.

OpenTUI already replaces the default SIGHUP behavior, so adding Hunk's listener does not change the signal.

Tests

test/pty/lifecycle.test.ts covers all three ways the terminal can go away. The macOS case uses revoke(2), which is not available on Linux. For the cross-platform PTY master-close case, the test allocates a PTY with bun:ffi and openpty, then starts Hunk on the slave with node:child_process. This is needed since tuistory/node-pty kills the child during teardown and cannot model a host closing the master while the child keeps the slave.

Test Before After
SIGHUP (Linux and macOS) Timed out waiting for exit Passes 5/5
PTY master close (Linux and macOS) Timed out waiting for exit Passes 5/5
TTY revoke (macOS) Timed out waiting for exit Passes 5/5
Non-TTY stdin still renders Rendered 341 bytes instead of at least 1,000 Passes 3/3

The SIGHUP case runs with brokering enabled, since disabling it removes the pending handle and the test would pass for the wrong reason.

Known unrelated failures

There are some test failures that can also be reproduce on 61cc6b1 (latest main branch by the time this pr is created) without this branch when running bun run test and bun run test:integration

@greptile-apps

greptile-apps Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

PR author is not in the allowed authors list.

@vercel

vercel Bot commented Aug 13, 2026

Copy link
Copy Markdown

@NiqhtFire is attempting to deploy a commit to the Modem Team on Vercel.

A member of the Team first needs to authorize it.

@acidclouds

acidclouds commented Aug 13, 2026

Copy link
Copy Markdown

Please merge this fix.
Without it hunk is unusable from lazygit

@benvinegar

Copy link
Copy Markdown
Member

Thanks for the thorough investigation here—the SIGHUP and PTY disconnect handling looks solid.

I found one blocker: OpenTUI also intercepts signals such as SIGQUIT and SIGPIPE, but Hunk only routes SIGINT, SIGTERM, and SIGHUP through shutdown(). With brokering enabled, I reproduced both SIGQUIT and SIGPIPE destroying the renderer while leaving the Hunk process alive.

Could we disable OpenTUI’s signal ownership with exitSignals: [] and have Hunk explicitly handle the signals it wants to shut down cleanly? A regression test for one of these additional signals would be helpful too.

Two smaller test notes:

  • The lifecycle tests only check that the PID disappears, so a crash also passes. It would be better to assert exit code 0.
  • The non-TTY test counts output bytes but doesn’t verify the process remains alive after rendering.

This comment was generated by Pi using gpt-5.6-sol

@NiqhtFire

Copy link
Copy Markdown
Author

Thanks, I disabled OpenTUI’s signal handling and moved clean shutdowns into Hunk itself, including SIGQUIT and SIGPIPE. Tests now cover those signals, assert exit code 0, and verify non-TTY rendering doesn’t exit early.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants