From cd50b5ded2e8d38409aae7e68846472322e6a33b Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 13 Feb 2026 03:20:25 +0100 Subject: [PATCH] fix(onboarding): exit cleanly after web ui hatch --- CHANGELOG.md | 6 +++++ src/terminal/restore.test.ts | 49 ++++++++++++++++++++++++++++++++++++ src/terminal/restore.ts | 7 ------ 3 files changed, 55 insertions(+), 7 deletions(-) create mode 100644 src/terminal/restore.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 53a4e95dc2..04b4caa4d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ Docs: https://docs.openclaw.ai +## 2026.2.13 (Unreleased) + +### Fixes + +- Onboarding/CLI: restore terminal state without resuming paused `stdin`, so onboarding exits cleanly after choosing Web UI and the installer returns instead of appearing stuck. + ## 2026.2.12 ### Changes diff --git a/src/terminal/restore.test.ts b/src/terminal/restore.test.ts new file mode 100644 index 0000000000..4b0b0d16c3 --- /dev/null +++ b/src/terminal/restore.test.ts @@ -0,0 +1,49 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; + +const clearActiveProgressLine = vi.hoisted(() => vi.fn()); + +vi.mock("./progress-line.js", () => ({ + clearActiveProgressLine, +})); + +import { restoreTerminalState } from "./restore.js"; + +describe("restoreTerminalState", () => { + const originalStdinIsTTY = process.stdin.isTTY; + const originalStdoutIsTTY = process.stdout.isTTY; + const originalSetRawMode = (process.stdin as { setRawMode?: (mode: boolean) => void }).setRawMode; + const originalResume = (process.stdin as { resume?: () => void }).resume; + const originalIsPaused = (process.stdin as { isPaused?: () => boolean }).isPaused; + + afterEach(() => { + vi.restoreAllMocks(); + Object.defineProperty(process.stdin, "isTTY", { + value: originalStdinIsTTY, + configurable: true, + }); + Object.defineProperty(process.stdout, "isTTY", { + value: originalStdoutIsTTY, + configurable: true, + }); + (process.stdin as { setRawMode?: (mode: boolean) => void }).setRawMode = originalSetRawMode; + (process.stdin as { resume?: () => void }).resume = originalResume; + (process.stdin as { isPaused?: () => boolean }).isPaused = originalIsPaused; + }); + + it("does not resume paused stdin while restoring raw mode", () => { + const setRawMode = vi.fn(); + const resume = vi.fn(); + const isPaused = vi.fn(() => true); + + Object.defineProperty(process.stdin, "isTTY", { value: true, configurable: true }); + Object.defineProperty(process.stdout, "isTTY", { value: false, configurable: true }); + (process.stdin as { setRawMode?: (mode: boolean) => void }).setRawMode = setRawMode; + (process.stdin as { resume?: () => void }).resume = resume; + (process.stdin as { isPaused?: () => boolean }).isPaused = isPaused; + + restoreTerminalState("test"); + + expect(setRawMode).toHaveBeenCalledWith(false); + expect(resume).not.toHaveBeenCalled(); + }); +}); diff --git a/src/terminal/restore.ts b/src/terminal/restore.ts index eb0742905b..c718c5932f 100644 --- a/src/terminal/restore.ts +++ b/src/terminal/restore.ts @@ -26,13 +26,6 @@ export function restoreTerminalState(reason?: string): void { } catch (err) { reportRestoreFailure("raw mode", err, reason); } - if (typeof stdin.isPaused === "function" && stdin.isPaused()) { - try { - stdin.resume(); - } catch (err) { - reportRestoreFailure("stdin resume", err, reason); - } - } } if (process.stdout.isTTY) {