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
7 changes: 5 additions & 2 deletions packages/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand Down
53 changes: 34 additions & 19 deletions packages/core/src/services/shellExecutionService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -1507,31 +1508,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 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;
// 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 };
Comment thread
scidomino marked this conversation as resolved.
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