Skip to content

Commit bd53951

Browse files
authored
fix(core): harden PTY resize against native crashes (#27496)
1 parent 5cac7c1 commit bd53951

2 files changed

Lines changed: 39 additions & 21 deletions

File tree

packages/cli/index.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,12 @@ import {
2121
// Tracking bug: https://github.com/microsoft/node-pty/issues/827
2222
process.on('uncaughtException', (error) => {
2323
if (error instanceof Error) {
24+
const message = error.message || '';
2425
const isPtyResizeError =
25-
error.message === 'Cannot resize a pty that has already exited';
26-
const isEbadfError = error.message.includes('EBADF');
26+
message === 'Cannot resize a pty that has already exited';
27+
const isEbadfError =
28+
message.includes('EBADF') ||
29+
(error as { code?: string }).code === 'EBADF';
2730
const isFromNodePty =
2831
error.stack?.includes('node-pty') || error.stack?.includes('PtyResize');
2932

packages/core/src/services/shellExecutionService.ts

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import {
3737
} from './sandboxManager.js';
3838
import type { SandboxConfig } from '../config/config.js';
3939
import { killProcessGroup } from '../utils/process-utils.js';
40+
import { isNodeError } from '../utils/errors.js';
4041
import {
4142
ExecutionLifecycleService,
4243
type ExecutionHandle,
@@ -1507,31 +1508,45 @@ export class ShellExecutionService {
15071508
}
15081509

15091510
const activePty = this.activePtys.get(pid);
1510-
if (activePty) {
1511+
if (!activePty) {
1512+
return;
1513+
}
1514+
1515+
// Skip Windows: process.kill(pid, 0) is heavy and native errors are catchable there.
1516+
if (process.platform !== 'win32') {
15111517
try {
1512-
activePty.ptyProcess.resize(cols, rows);
1513-
activePty.headlessTerminal.resize(cols, rows);
1518+
process.kill(pid, 0);
15141519
} catch (e) {
1515-
// Ignore errors if the pty has already exited, which can happen
1516-
// due to a race condition between the exit event and this call.
1517-
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
1518-
const err = e as { code?: string; message?: string };
1519-
const isEsrch = err.code === 'ESRCH';
1520-
const isEbadf = err.code === 'EBADF' || err.message?.includes('EBADF');
1521-
const isWindowsPtyError = err.message?.includes(
1522-
'Cannot resize a pty that has already exited',
1523-
);
1524-
1525-
if (isEsrch || isEbadf || isWindowsPtyError) {
1526-
// On Unix, we get an ESRCH or EBADF error.
1527-
// On Windows, we get a message-based error.
1528-
// In both cases, it's safe to ignore.
1529-
} else {
1530-
throw e;
1520+
// Bail only if the process is explicitly confirmed dead (ESRCH).
1521+
if (isNodeError(e) && e.code === 'ESRCH') {
1522+
return;
15311523
}
15321524
}
15331525
}
15341526

1527+
try {
1528+
activePty.ptyProcess.resize(cols, rows);
1529+
activePty.headlessTerminal.resize(cols, rows);
1530+
} catch (e) {
1531+
// Ignore errors if the pty has already exited, which can happen
1532+
// due to a race condition between the exit event and this call.
1533+
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
1534+
const err = e as { code?: string; message?: string };
1535+
const isEsrch = err.code === 'ESRCH';
1536+
const isEbadf = err.code === 'EBADF' || err.message?.includes('EBADF');
1537+
const isWindowsPtyError = err.message?.includes(
1538+
'Cannot resize a pty that has already exited',
1539+
);
1540+
1541+
if (isEsrch || isEbadf || isWindowsPtyError) {
1542+
// On Unix, we get an ESRCH or EBADF error.
1543+
// On Windows, we get a message-based error.
1544+
// In both cases, it's safe to ignore.
1545+
} else {
1546+
throw e;
1547+
}
1548+
}
1549+
15351550
// Force emit the new state after resize
15361551
if (activePty) {
15371552
const endLine = activePty.headlessTerminal.buffer.active.length;

0 commit comments

Comments
 (0)