From 965a8b85f0b525d3dc6487046d7d228de3a65941 Mon Sep 17 00:00:00 2001 From: Claude Agent Date: Mon, 2 Mar 2026 07:36:54 +0000 Subject: [PATCH] fix(ssh): restore exitAlternateScreenBuffer to prevent character loss on session open Characters were being lost at the start of an SSH session due to two issues introduced in a previous refactor: 1. exitAlternateScreenBuffer() was removed from releaseTerminal(), causing SSH to run inside Ink's alternate screen buffer. Any Ink re-renders during the connection setup wrote ANSI sequences to the same buffer, corrupting terminal state and swallowing early keystrokes. 2. setRawMode(false) was called before spawning SSH, which invokes tcsetattr(TCSAFLUSH) under the hood. TCSAFLUSH discards pending kernel input and switches the terminal to canonical (line-buffered) mode. Characters typed during the SSH connection handshake were therefore either discarded or held until Enter was pressed, never reaching SSH. Fix: call exitAlternateScreenBuffer() first in releaseTerminal() so SSH runs in the normal screen buffer (isolated from Ink's rendering), and remove the setRawMode(false) call so the terminal stays in raw mode throughout the transition. Co-Authored-By: Claude Sonnet 4.6 --- src/components/InteractiveSpawn.tsx | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/components/InteractiveSpawn.tsx b/src/components/InteractiveSpawn.tsx index f40b9de7..17402301 100644 --- a/src/components/InteractiveSpawn.tsx +++ b/src/components/InteractiveSpawn.tsx @@ -8,6 +8,7 @@ import { spawn, ChildProcess } from "child_process"; import { showCursor, clearScreen, + exitAlternateScreenBuffer, enterAlternateScreenBuffer, } from "../utils/screen.js"; import { processUtils } from "../utils/processUtils.js"; @@ -24,14 +25,20 @@ interface InteractiveSpawnProps { * This directly manipulates stdin to bypass Ink's input handling. */ function releaseTerminal(): void { + // Exit alternate screen buffer so the subprocess runs in the normal screen buffer. + // This prevents Ink's ongoing renders (which target the alt buffer) from + // interfering with the subprocess's terminal I/O, which was causing characters + // to be lost at the start of the session. + exitAlternateScreenBuffer(); + // Pause stdin to stop Ink from reading input process.stdin.pause(); - // Disable raw mode so the subprocess can control terminal echo and line buffering - // SSH needs to set its own terminal modes - if (processUtils.stdin.isTTY && processUtils.stdin.setRawMode) { - processUtils.stdin.setRawMode(false); - } + // NOTE: We intentionally do NOT call setRawMode(false) here. Leaving the terminal + // in raw mode means characters typed during the subprocess connection setup remain + // immediately available in the kernel buffer (no canonical line-buffering). Calling + // setRawMode(false) would invoke tcsetattr(TCSAFLUSH) which discards pending input + // and switches to canonical mode where keystrokes are held until Enter is pressed. // Reset terminal attributes (SGR reset) - clears any colors/styles Ink may have set if (processUtils.stdout.isTTY) { @@ -40,11 +47,6 @@ function releaseTerminal(): void { // Show cursor - Ink may have hidden it, and subprocesses expect it to be visible showCursor(); - - // Flush stdout to ensure all pending writes are complete before handoff - if (processUtils.stdout.isTTY) { - processUtils.stdout.write(""); - } } /**