From 37619af3fbfb31cdc1a8a2c4b3af5a207b24f7bf Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Fri, 7 Aug 2026 11:50:48 -0700 Subject: [PATCH] fix(terminal): scope host-gone error matching --- .../terminal-pane/TerminalErrorToast.test.ts | 16 +++++++ .../terminal-pane/TerminalErrorToast.tsx | 46 +++++++++++-------- 2 files changed, 42 insertions(+), 20 deletions(-) diff --git a/src/renderer/src/components/terminal-pane/TerminalErrorToast.test.ts b/src/renderer/src/components/terminal-pane/TerminalErrorToast.test.ts index 7366f5b6594..9a0555bcca4 100644 --- a/src/renderer/src/components/terminal-pane/TerminalErrorToast.test.ts +++ b/src/renderer/src/components/terminal-pane/TerminalErrorToast.test.ts @@ -74,6 +74,22 @@ describe('humanizeTerminalError', () => { expect(humanized).not.toContain('orca-terminal-host-v30') expect(humanized).toContain('Open a new terminal to continue') }) + + it('only replaces exact host-gone markers in aggregated errors', () => { + const humanized = humanizeTerminalError('terminal_host_gone\nterminal_host_gone_extra') + expect(humanized).toContain('Open a new terminal to continue') + expect(humanized).toContain('\nterminal_host_gone_extra') + }) + + it.each(['ENOENT', 'ECONNREFUSED'])( + 'does not combine a %s connection failure with a host endpoint on another line', + (code) => { + const aggregated = + `connect ${code} \\\\?\\pipe\\unrelated\n` + 'orca-terminal-host-v30-14cb7f94b511' + expect(isExplainedTerminalError(aggregated)).toBe(false) + expect(humanizeTerminalError(aggregated)).toBe(aggregated) + } + ) }) describe('isExplainedTerminalError', () => { diff --git a/src/renderer/src/components/terminal-pane/TerminalErrorToast.tsx b/src/renderer/src/components/terminal-pane/TerminalErrorToast.tsx index ef98350c8d5..68e6485cff0 100644 --- a/src/renderer/src/components/terminal-pane/TerminalErrorToast.tsx +++ b/src/renderer/src/components/terminal-pane/TerminalErrorToast.tsx @@ -14,10 +14,10 @@ const STALE_DAEMON_CWD_MARKERS = [ ] // Thrown by ipc/pty.ts when a persisted pane owner can't be proven alive or dead (STA-3536). const PANE_OWNER_UNVERIFIED_MARKER = 'terminal_pane_owner_unverified' -const TERMINAL_HOST_GONE_MARKER = 'terminal_host_gone' const TERMINAL_HOST_GONE_PATTERN = /(?:^|[^a-z0-9_])terminal_host_gone(?:$|[^a-z0-9_])/ -const LEGACY_TERMINAL_HOST_ENDPOINT_MARKER = 'orca-terminal-host-v' -const LEGACY_TERMINAL_HOST_GONE_PATTERN = /connect (?:ENOENT|ECONNREFUSED) [^\n]*/ +const TERMINAL_HOST_GONE_REPLACE_PATTERN = /(^|[^a-z0-9_])terminal_host_gone(?=$|[^a-z0-9_])/g +const LEGACY_TERMINAL_HOST_GONE_PATTERN = + /(^|[^a-z])connect (?:ENOENT|ECONNREFUSED) [^\r\n]*orca-terminal-host-v[^\r\n]*/i function isSshError(error: string): boolean { return error.startsWith(SSH_PREFIX) || error.includes(SSH_RELAY_LOST_MARKER) @@ -49,11 +49,12 @@ export function shouldOfferDaemonRestart(error: string): boolean { } export function isExplainedTerminalError(error: string): boolean { - return ( - TERMINAL_HOST_GONE_PATTERN.test(error) || - (error.includes(LEGACY_TERMINAL_HOST_ENDPOINT_MARKER) && - LEGACY_TERMINAL_HOST_GONE_PATTERN.test(error)) - ) + return error + .split('\n') + .some( + (line) => + TERMINAL_HOST_GONE_PATTERN.test(line) || LEGACY_TERMINAL_HOST_GONE_PATTERN.test(line) + ) } /** Swaps raw daemon-boundary codes for copy a user can act on. */ @@ -68,20 +69,25 @@ export function humanizeTerminalError(error: string): string { ) ) } - if (isExplainedTerminalError(humanized)) { - const explanation = translate( - 'auto.components.terminal.pane.TerminalErrorToast.e16012e31e', - 'The terminal daemon that owned this session exited, so the session and its scrollback could not be recovered. Open a new terminal to continue.' - ) - humanized = humanized.replaceAll(TERMINAL_HOST_GONE_MARKER, explanation) - if (humanized.includes(LEGACY_TERMINAL_HOST_ENDPOINT_MARKER)) { - humanized = humanized - .split('\n') - .map((line) => line.replace(LEGACY_TERMINAL_HOST_GONE_PATTERN, explanation)) - .join('\n') - } + if (!isExplainedTerminalError(humanized)) { + return humanized } + const explanation = translate( + 'auto.components.terminal.pane.TerminalErrorToast.e16012e31e', + 'The terminal daemon that owned this session exited, so the session and its scrollback could not be recovered. Open a new terminal to continue.' + ) return humanized + .split('\n') + .map((line) => + line + .replace(TERMINAL_HOST_GONE_REPLACE_PATTERN, (_match, prefix: string) => + prefix.concat(explanation) + ) + .replace(LEGACY_TERMINAL_HOST_GONE_PATTERN, (_match, prefix: string) => + prefix.concat(explanation) + ) + ) + .join('\n') } export function TerminalErrorToast({