From db3432ac3e34e9172b70901d97677666e463abad Mon Sep 17 00:00:00 2001 From: Reflex Date: Fri, 19 Jun 2026 08:08:41 +0000 Subject: [PATCH] fix(cli): only exit alternate screen buffer on SIGINT if it was entered The global SIGINT handler unconditionally sent \x1b[?1049l even when no TUI was active (e.g. `rli d ssh` waiting for a provisioning devbox). Terminals restore their saved cursor position on that sequence, so Ctrl+C during a plain-text wait loop jumped the cursor to the top of the screen and left output garbled. Track whether the alternate screen buffer is actually active in screen.ts and guard the SIGINT handler so it only exits the buffer when needed. Co-Authored-By: Claude Sonnet 4.6 --- src/cli.ts | 14 +++++++++++--- src/utils/screen.ts | 9 +++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index cc2fe9fe..45e06b7e 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,14 +1,22 @@ #!/usr/bin/env node -import { exitAlternateScreenBuffer } from "./utils/screen.js"; +import { + exitAlternateScreenBuffer, + isInAlternateScreenBuffer, +} from "./utils/screen.js"; import { processUtils } from "./utils/processUtils.js"; import { createProgram } from "./utils/commands.js"; import { getApiKeyErrorMessage, checkBaseDomain } from "./utils/config.js"; // Global Ctrl+C handler to ensure it always exits processUtils.on("SIGINT", () => { - // Force exit immediately, clearing alternate screen buffer - exitAlternateScreenBuffer(); + // Only restore the alternate screen buffer if we actually entered it. + // Unconditionally sending the exit sequence when no TUI is active causes + // the terminal to restore a stale saved-cursor position, jumping the + // cursor and garbling any plain-text output (e.g. `rli d ssh` wait loop). + if (isInAlternateScreenBuffer()) { + exitAlternateScreenBuffer(); + } processUtils.stdout.write("\n"); processUtils.exit(130); // Standard exit code for SIGINT }); diff --git a/src/utils/screen.ts b/src/utils/screen.ts index e89a93d5..18cae86f 100644 --- a/src/utils/screen.ts +++ b/src/utils/screen.ts @@ -9,12 +9,20 @@ import { processUtils } from "./processUtils.js"; +let _inAlternateScreenBuffer = false; + +/** Returns true if the alternate screen buffer is currently active. */ +export function isInAlternateScreenBuffer(): boolean { + return _inAlternateScreenBuffer; +} + /** * Enter the alternate screen buffer. * This provides a fullscreen experience where content won't mix with * previous terminal output. Like vim or top. */ export function enterAlternateScreenBuffer(): void { + _inAlternateScreenBuffer = true; processUtils.stdout.write("\x1b[?1049h"); } @@ -23,6 +31,7 @@ export function enterAlternateScreenBuffer(): void { * This returns the terminal to its original state before enterAlternateScreen() was called. */ export function exitAlternateScreenBuffer(): void { + _inAlternateScreenBuffer = false; processUtils.stdout.write("\x1b[?1049l"); }