Skip to content

Windows: TUI exit (Ctrl+C) leaves the terminal garbled -- alt-screen not restored cleanly on conpty #205

Closed

Description

@arelchan

Summary

On Windows (PowerShell / conpty), exiting the TUI with Ctrl+C leaves the terminal garbled -- the last TUI frame overlaps the shell prompt (stale content bleeding onto the primary screen). cls cleans it up, so it is a terminal-restore problem on exit, not lost data.

Filed from code analysis on macOS; I cannot reproduce Windows conpty locally, so the exact trigger needs a Windows repro to confirm. Everything below is from reading the exit path.

Why (from the code)

The TUI renders into the alternate screen (\x1b[?1049h). Clean exit must leave the alt screen and reset modes exactly once, ordered before the resume hint -- otherwise the final frame / a redundant alt-screen-leave lands on the primary buffer as overlapping output.

That teardown is carefully tuned, but for POSIX:

  • ui-tui/packages/hermes-ink/src/ink/ink.tsx -- gracefulShutdown / cleanupTerminalModes / detachForShutdown exist specifically to avoid "2-3 redundant EXIT_ALT_SCREEN sequences landing on the main screen AFTER printResumeHint()" (see the detachForShutdown docstring, ~L1275-1290). The ordering guarantee is validated against signal-exit's deferred-unmount behavior.
  • The only win32 branch (ink.tsx:2409) is an early return in the stdin-drain helper ("No /dev/tty on Windows; CONIN$ doesn't support O_NONBLOCK"), so Windows skips part of the POSIX cleanup path.
  • writeSync(1, EXIT_ALT_SCREEN) (ink.tsx:~2177) and the reset in ui-tui/src/lib/terminalModes.ts (TERMINAL_MODE_RESET, includes \x1b[?1049l) assume POSIX fd/ordering semantics.
  • There are multiple exit paths that must not double-fire: the app's die() (ui-tui/src/app/useMainApp.ts:~392 -- ink exit() + process.exit(0)), setupGracefulExit's SIGINT handler + resetTerminalModes() (ui-tui/src/entry.tsx:95-112), and signal-exit's onExit(this.unmount) (ink.tsx:344).

On Windows conpty these assumptions don't hold: no /dev/tty, Ctrl+C delivery in raw mode differs, and conpty's alternate-screen / cursor-restore semantics differ. The "exactly one EXIT_ALT_SCREEN, ordered before the resume hint" guarantee breaks, so a redundant/mis-ordered alt-screen-leave plus the final render bleeds onto the primary buffer -> overlapping output.

Steps to reproduce

  1. Windows, PowerShell (Windows Terminal or conhost).
  2. raven (enter TUI).
  3. Press Ctrl+C to exit.
  4. Prompt returns with garbled / overlapping leftover TUI output.

Expected

Exiting the TUI (Ctrl+C or otherwise) restores the primary screen cleanly, same as POSIX.

Workaround

cls (Clear-Host) clears the overlap. If input is also affected (mouse modes left on), open a fresh tab.

Suggested direction

Make the alt-screen leave + terminal reset emit exactly once and ordered before the resume hint on Windows conpty too -- i.e. give the shutdown ordering guards (detachForShutdown / isUnmounted / cancel-pending-render) a Windows path instead of relying on POSIX signal-exit timing, and cover the cleanup the win32 early-return (ink.tsx:2409) skips.

Environment

Windows, PowerShell (conpty). Reproduced by a user on Ctrl+C exit.

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions