From 7496e229f0e4dd5cf68da0b9338c9a8917ad3f03 Mon Sep 17 00:00:00 2001 From: Tommaso Sciortino Date: Thu, 28 May 2026 09:16:24 -0700 Subject: [PATCH 1/2] fix(core): harden PTY resize against native crashes (#27496) # Conflicts: # packages/cli/index.ts # packages/core/src/services/shellExecutionService.ts --- packages/cli/index.ts | 18 +++++++++ .../src/services/shellExecutionService.ts | 39 +++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/packages/cli/index.ts b/packages/cli/index.ts index f13d4707b0f..658d2026593 100644 --- a/packages/cli/index.ts +++ b/packages/cli/index.ts @@ -20,6 +20,7 @@ import { // Suppress known race condition error in node-pty on Windows // Tracking bug: https://github.com/microsoft/node-pty/issues/827 process.on('uncaughtException', (error) => { +<<<<<<< HEAD if ( process.platform === 'win32' && error instanceof Error && @@ -28,6 +29,23 @@ process.on('uncaughtException', (error) => { // This error happens on Windows with node-pty when resizing a pty that has just exited. // It is a race condition in node-pty that we cannot prevent, so we silence it. return; +======= + if (error instanceof Error) { + const message = error.message || ''; + const isPtyResizeError = + message === 'Cannot resize a pty that has already exited'; + const isEbadfError = + message.includes('EBADF') || + (error as { code?: string }).code === 'EBADF'; + const isFromNodePty = + error.stack?.includes('node-pty') || error.stack?.includes('PtyResize'); + + if ((isPtyResizeError || isEbadfError) && isFromNodePty) { + // This error happens with node-pty when resizing a pty that has just exited. + // It is a race condition in node-pty that we cannot prevent, so we silence it. + return; + } +>>>>>>> bd53951dc (fix(core): harden PTY resize against native crashes (#27496)) } // For other errors, we rely on the default behavior, but since we attached a listener, diff --git a/packages/core/src/services/shellExecutionService.ts b/packages/core/src/services/shellExecutionService.ts index 2408dc3e11a..3c3c12a1385 100644 --- a/packages/core/src/services/shellExecutionService.ts +++ b/packages/core/src/services/shellExecutionService.ts @@ -38,6 +38,7 @@ import { } from './sandboxManager.js'; import type { SandboxConfig } from '../config/config.js'; import { killProcessGroup } from '../utils/process-utils.js'; +import { isNodeError } from '../utils/errors.js'; import { ExecutionLifecycleService, type ExecutionHandle, @@ -1541,6 +1542,7 @@ export class ShellExecutionService { } const activePty = this.activePtys.get(pid); +<<<<<<< HEAD if (activePty) { try { activePty.ptyProcess.resize(cols, rows); @@ -1561,10 +1563,47 @@ export class ShellExecutionService { // In both cases, it's safe to ignore. } else { throw e; +======= + if (!activePty) { + return; + } + + // Skip Windows: process.kill(pid, 0) is heavy and native errors are catchable there. + if (process.platform !== 'win32') { + try { + process.kill(pid, 0); + } catch (e) { + // Bail only if the process is explicitly confirmed dead (ESRCH). + if (isNodeError(e) && e.code === 'ESRCH') { + return; +>>>>>>> bd53951dc (fix(core): harden PTY resize against native crashes (#27496)) } } } + try { + activePty.ptyProcess.resize(cols, rows); + activePty.headlessTerminal.resize(cols, rows); + } catch (e) { + // Ignore errors if the pty has already exited, which can happen + // due to a race condition between the exit event and this call. + // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion + const err = e as { code?: string; message?: string }; + const isEsrch = err.code === 'ESRCH'; + const isEbadf = err.code === 'EBADF' || err.message?.includes('EBADF'); + const isWindowsPtyError = err.message?.includes( + 'Cannot resize a pty that has already exited', + ); + + if (isEsrch || isEbadf || isWindowsPtyError) { + // On Unix, we get an ESRCH or EBADF error. + // On Windows, we get a message-based error. + // In both cases, it's safe to ignore. + } else { + throw e; + } + } + // Force emit the new state after resize if (activePty) { const endLine = activePty.headlessTerminal.buffer.active.length; From fb01147f02ba75ac0cbd5b7f3f2b10f17d94e8fa Mon Sep 17 00:00:00 2001 From: Tommaso Sciortino Date: Thu, 28 May 2026 09:34:09 -0700 Subject: [PATCH 2/2] chore: resolve merge conflicts in PTY resize hardening --- packages/cli/index.ts | 11 --------- .../src/services/shellExecutionService.ts | 23 ------------------- 2 files changed, 34 deletions(-) diff --git a/packages/cli/index.ts b/packages/cli/index.ts index 658d2026593..7cd28116e11 100644 --- a/packages/cli/index.ts +++ b/packages/cli/index.ts @@ -20,16 +20,6 @@ import { // Suppress known race condition error in node-pty on Windows // Tracking bug: https://github.com/microsoft/node-pty/issues/827 process.on('uncaughtException', (error) => { -<<<<<<< HEAD - if ( - process.platform === 'win32' && - error instanceof Error && - error.message === 'Cannot resize a pty that has already exited' - ) { - // This error happens on Windows with node-pty when resizing a pty that has just exited. - // It is a race condition in node-pty that we cannot prevent, so we silence it. - return; -======= if (error instanceof Error) { const message = error.message || ''; const isPtyResizeError = @@ -45,7 +35,6 @@ process.on('uncaughtException', (error) => { // It is a race condition in node-pty that we cannot prevent, so we silence it. return; } ->>>>>>> bd53951dc (fix(core): harden PTY resize against native crashes (#27496)) } // For other errors, we rely on the default behavior, but since we attached a listener, diff --git a/packages/core/src/services/shellExecutionService.ts b/packages/core/src/services/shellExecutionService.ts index 3c3c12a1385..e12aa367ae3 100644 --- a/packages/core/src/services/shellExecutionService.ts +++ b/packages/core/src/services/shellExecutionService.ts @@ -1542,28 +1542,6 @@ export class ShellExecutionService { } const activePty = this.activePtys.get(pid); -<<<<<<< HEAD - if (activePty) { - try { - activePty.ptyProcess.resize(cols, rows); - activePty.headlessTerminal.resize(cols, rows); - } catch (e) { - // Ignore errors if the pty has already exited, which can happen - // due to a race condition between the exit event and this call. - // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion - const err = e as { code?: string; message?: string }; - const isEsrch = err.code === 'ESRCH'; - const isWindowsPtyError = err.message?.includes( - 'Cannot resize a pty that has already exited', - ); - - if (isEsrch || isWindowsPtyError) { - // On Unix, we get an ESRCH error. - // On Windows, we get a message-based error. - // In both cases, it's safe to ignore. - } else { - throw e; -======= if (!activePty) { return; } @@ -1576,7 +1554,6 @@ export class ShellExecutionService { // Bail only if the process is explicitly confirmed dead (ESRCH). if (isNodeError(e) && e.code === 'ESRCH') { return; ->>>>>>> bd53951dc (fix(core): harden PTY resize against native crashes (#27496)) } } }