From 1af9d753964a3abaa97e71c3d0850b1c52d95046 Mon Sep 17 00:00:00 2001 From: kumarabhirup Date: Sun, 8 Mar 2026 21:45:17 -0700 Subject: [PATCH] fix(terminal): add try/catch around PTY spawn to prevent silent hangs Without error handling, if node-pty require or pty.spawn throws (bad permissions, missing cwd, stale server), the client gets no response and the terminal hangs with a blinking cursor. Now sends an exit event back. --- .../components/terminal/terminal-drawer.tsx | 1 - apps/web/lib/terminal-server.ts | 40 +++++++++++++------ 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/apps/web/app/components/terminal/terminal-drawer.tsx b/apps/web/app/components/terminal/terminal-drawer.tsx index 3302224557a..b4211fac18b 100644 --- a/apps/web/app/components/terminal/terminal-drawer.tsx +++ b/apps/web/app/components/terminal/terminal-drawer.tsx @@ -192,7 +192,6 @@ function TerminalViewport({ } catch { return; } - if (msg.type === "output" && msg.data) { terminal.write(msg.data); } else if (msg.type === "exit") { diff --git a/apps/web/lib/terminal-server.ts b/apps/web/lib/terminal-server.ts index 75a38482c66..6860577cc03 100644 --- a/apps/web/lib/terminal-server.ts +++ b/apps/web/lib/terminal-server.ts @@ -54,21 +54,35 @@ function shellArgs(shell: string): string[] { function spawnTerminal(ws: WebSocket, cols: number, rows: number, cwd?: string) { ensureSpawnHelperExecutable(); - // eslint-disable-next-line @typescript-eslint/no-require-imports - const nodePty = require("node-pty") as typeof import("node-pty"); + let nodePty: typeof import("node-pty"); + try { + // eslint-disable-next-line @typescript-eslint/no-require-imports + nodePty = require("node-pty") as typeof import("node-pty"); + } catch { + ws.send(JSON.stringify({ type: "exit", exitCode: 1, signal: null })); + return; + } const shell = defaultShell(); - const pty = nodePty.spawn(shell, shellArgs(shell), { - name: "xterm-256color", - cols, - rows, - cwd: cwd || process.env.HOME || process.cwd(), - env: Object.fromEntries( - Object.entries(process.env).filter( - ([, v]) => v !== undefined, - ), - ) as Record, - }); + const spawnCwd = cwd || process.env.HOME || process.cwd(); + + let pty: import("node-pty").IPty; + try { + pty = nodePty.spawn(shell, shellArgs(shell), { + name: "xterm-256color", + cols, + rows, + cwd: spawnCwd, + env: Object.fromEntries( + Object.entries(process.env).filter( + ([, v]) => v !== undefined, + ), + ) as Record, + }); + } catch { + ws.send(JSON.stringify({ type: "exit", exitCode: 1, signal: null })); + return; + } const session: TerminalSession = { pty, ws }; sessions.set(ws, session);