diff --git a/packages/cli/index.ts b/packages/cli/index.ts index 2f0796c7d9..a1f86ee4a7 100644 --- a/packages/cli/index.ts +++ b/packages/cli/index.ts @@ -21,9 +21,12 @@ import { // Tracking bug: https://github.com/microsoft/node-pty/issues/827 process.on('uncaughtException', (error) => { if (error instanceof Error) { + const message = error.message || ''; const isPtyResizeError = - error.message === 'Cannot resize a pty that has already exited'; - const isEbadfError = error.message.includes('EBADF'); + 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'); diff --git a/packages/core/src/services/shellExecutionService.ts b/packages/core/src/services/shellExecutionService.ts index 2f9a6c4e52..1e8bff00e6 100644 --- a/packages/core/src/services/shellExecutionService.ts +++ b/packages/core/src/services/shellExecutionService.ts @@ -37,6 +37,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, @@ -1507,31 +1508,45 @@ export class ShellExecutionService { } const activePty = this.activePtys.get(pid); - 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 isEbadf = err.code === 'EBADF' || err.message?.includes('EBADF'); - const isWindowsPtyError = err.message?.includes( - 'Cannot resize a pty that has already exited', - ); + if (!activePty) { + return; + } - 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; + // 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; } } } + 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;