fix(tui): exit when the terminal hangs up instead of spinning forever - #258
Merged
Merged
Conversation
Closing a terminal window with `kit tui` running left the process alive, orphaned, and pinning a CPU core until killed by hand. On hangup the TUI tears down and `Events::drop` joins the input reader thread. That thread sits in crossterm's `event::poll`, whose read loop retries a dead terminal descriptor forever (it only breaks on WouldBlock), so the poll never returns, the stop flag is never rechecked, and the join never completes. Bound the wait: the reader signals its exit by dropping a channel sender, and teardown detaches a reader that has not exited within two seconds. A live reader still returns within one 25ms poll interval and is joined as before, so terminal ownership handoff to authentication is unchanged. The PTY lifecycle test gains a hangup case that closes the master and requires the child to exit. The master is now close-on-exec so the child no longer holds its own terminal open. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
danielkov
previously approved these changes
Oct 1, 2026
daviddanialy
enabled auto-merge (squash)
October 1, 2026 17:17
With the input reader no longer blocking teardown, a hangup now reaches `leave`. `ratatui::restore` reports a failed restore with `eprintln!`, which panics when stderr is the same dead terminal. The panic skipped the session close that follows teardown and exited with status 101. Report the failure with a write whose error is ignored instead. The PTY hangup case now runs `leave` as well, so it fails if teardown panics. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
daviddanialy
dismissed stale reviews from danielkov and kit-code-agent[bot]
via
October 1, 2026 17:29
197fb73
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Closing a terminal window or tab while
kit tuiis running leaves the process alive, reparented to launchd, and pinning a full CPU core until it is killed by hand. Nine of these had accumulated on one machine over two days, each at ~100% CPU.This is a regression from #239, which introduced the dedicated
kit-terminal-inputreader thread. Kit 0.2.2 does not have it.Cause
Events::dropsets the stop flag and joins the reader thread.event::poll. Its tty read loop only breaks onWouldBlock; on a dead terminalread()returns EOF (or an error) immediately, so the loop retries forever andevent::pollnever returns.A stack sample of an affected process shows the main thread in
Events::drop→JoinHandle::joinand the reader thread inreadunder crossterm'stry_read.The unconditional join was deliberate (it hands terminal ownership back before authentication or restore), but it assumed
event::poll(25ms)always returns.Fix
Bound the join. The reader thread holds a channel sender that is dropped when it returns or unwinds;
Events::dropwaits up to two seconds for that and otherwise detaches the thread so the process can exit.Fixing the join makes the rest of teardown reachable after a hangup, which exposed a second problem:
leavecallsratatui::restore, which reports a failed restore witheprintln!. With stderr on the same dead terminal that panics, skipping the session close that follows and exiting with status 101.leavenow callsratatui::try_restoreand reports the failure with a write whose error is ignored.Testing
hangupcase in the PTY lifecycle test: the parent closes the PTY master, the child takes the SIGHUP through the realStopboundary, dropsEvents, runsleave, and must exit with status 0. It times out without the join fix, exits 101 without the restore fix, and passes with both.kit tuistarted in a PTY whose master is then closed exits in about 2s with status 0 with this change; the released 0.2.6 binary is still running after 15s.mise run lintpasses.mise run test -- --lib tui::has two failures locally (catalog_returns_canonical_workspace_root, and intermittentlyconnected_terminal_authentication_returns_the_process_exit_status); both fail the same way onmainwithout this change.Notes
tui::keyboard_tests::negotiated_shifted_characters_reach_composeron Linux: itspollon the PTY master returnedEINTR. That test does not go through the changed code.🤖 Generated with Claude Code