From f203b175b61085a2d8d9daedb95e1a8b70b319e2 Mon Sep 17 00:00:00 2001 From: jeankrlo2929 Date: Tue, 7 Apr 2026 02:51:48 -0400 Subject: [PATCH] fix(tui): reset terminal state on crash/exit (Windows) On Windows, when OpenCode exits unexpectedly or crashes, the terminal is left in a broken state showing raw ANSI escape sequences as literal text (e.g. "555;59;86m") and the cursor may be hidden. Add resetTerminal() in thread.ts and attach.ts that: - Sends \x1b[0m to reset all ANSI attributes (colors, bold, etc.) - Sends \x1b[?25h to restore the cursor - Sends \x1b[?1049l to exit the alternate screen buffer - Sends \x1b[?7h to re-enable auto-wrap Also registers a process.on("exit") handler in thread.ts as a backstop that runs even on process.exit() or uncaught exceptions. Fixes: #21277 --- packages/opencode/src/cli/cmd/tui/attach.ts | 11 +++++++++++ packages/opencode/src/cli/cmd/tui/thread.ts | 14 ++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/packages/opencode/src/cli/cmd/tui/attach.ts b/packages/opencode/src/cli/cmd/tui/attach.ts index e892f9922d1b..04bfe48dd938 100644 --- a/packages/opencode/src/cli/cmd/tui/attach.ts +++ b/packages/opencode/src/cli/cmd/tui/attach.ts @@ -6,6 +6,16 @@ import { TuiConfig } from "@/config/tui" import { Instance } from "@/project/instance" import { existsSync } from "fs" +function resetTerminal() { + if (!process.stdout.isTTY) return + process.stdout.write( + "\x1b[0m" + // reset all attributes (colors, bold, etc.) + "\x1b[?25h" + // show cursor + "\x1b[?1049l" + // exit alternate screen buffer + "\x1b[?7h", // re-enable auto-wrap + ) +} + export const AttachCommand = cmd({ command: "attach ", describe: "attach to a running opencode server", @@ -83,6 +93,7 @@ export const AttachCommand = cmd({ }) } finally { unguard?.() + resetTerminal() } }, }) diff --git a/packages/opencode/src/cli/cmd/tui/thread.ts b/packages/opencode/src/cli/cmd/tui/thread.ts index 3bb56937a6cb..9f26f39b69a7 100644 --- a/packages/opencode/src/cli/cmd/tui/thread.ts +++ b/packages/opencode/src/cli/cmd/tui/thread.ts @@ -21,6 +21,19 @@ declare global { const OPENCODE_WORKER_PATH: string } +function resetTerminal() { + if (!process.stdout.isTTY) return + process.stdout.write( + "\x1b[0m" + // reset all attributes (colors, bold, etc.) + "\x1b[?25h" + // show cursor + "\x1b[?1049l" + // exit alternate screen buffer + "\x1b[?7h", // re-enable auto-wrap + ) +} + +// Backstop: runs even on process.exit() or uncaught crash +process.on("exit", resetTerminal) + type RpcClient = ReturnType> function createWorkerFetch(client: RpcClient): typeof fetch { @@ -226,6 +239,7 @@ export const TuiThreadCommand = cmd({ } } finally { unguard?.() + resetTerminal() } process.exit(0) },