Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 15 additions & 8 deletions packages/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,21 @@ 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) => {
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 =
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;
}
}

// For other errors, we rely on the default behavior, but since we attached a listener,
Expand Down
52 changes: 34 additions & 18 deletions packages/core/src/services/shellExecutionService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -1541,30 +1542,45 @@ export class ShellExecutionService {
}

const activePty = this.activePtys.get(pid);
if (activePty) {
if (!activePty) {
return;
}

// Skip Windows: process.kill(pid, 0) is heavy and native errors are catchable there.
if (process.platform !== 'win32') {
try {
activePty.ptyProcess.resize(cols, rows);
activePty.headlessTerminal.resize(cols, rows);
process.kill(pid, 0);
} 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;
// 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;
Expand Down
Loading