Improve PTY exhaustion recovery messages (#1931)

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Brennan Benson
2026-05-15 10:50:22 -07:00
committed by GitHub
co-authored by Orca
parent 8cca091986
commit 60656e7a6e
2 changed files with 96 additions and 11 deletions
+46 -6
View File
@@ -1,6 +1,22 @@
import { describe, expect, it } from 'vitest'
import { addNodePtyRecoveryHint, parseNodePtyDiagnostic } from './node-pty-error-hints'
const PTY_ALLOCATION_HINT = [
'Your system cannot allocate any more pty devices.',
'',
'Orca requires a pty device to launch a new terminal. This error is usually due to having too many terminal windows or terminal sessions open, either in Orca or another program.',
'',
'Free up some pty devices and try again.'
].join('\n')
const TERMINAL_PROCESS_LIMIT_HINT = [
'Your system cannot start another terminal process.',
'',
'This is usually due to having too many terminal sessions or other processes running.',
'',
'Close unused terminals or quit unused processes and try again.'
].join('\n')
describe('node-pty diagnostic error hints', () => {
it('parses the native step and errno without dropping the original message', () => {
const message =
@@ -16,18 +32,42 @@ describe('node-pty diagnostic error hints', () => {
const message =
"node-pty: open_slave failed: EMFILE (errno 24, Too many open files) - slave='/dev/ttys003'"
expect(addNodePtyRecoveryHint(message)).toBe(
`Daemon hit the file-descriptor limit. Restart the daemon. ${message}`
)
expect(addNodePtyRecoveryHint(message)).toBe(`${PTY_ALLOCATION_HINT} ${message}`)
})
it('hints when the system cannot allocate a pty master', () => {
const message =
'node-pty: posix_openpt failed: ENFILE (errno 23, Too many open files in system)'
expect(addNodePtyRecoveryHint(message)).toBe(`${PTY_ALLOCATION_HINT} ${message}`)
})
it('hints local wrapped spawn errors from pty allocation failures', () => {
const message =
'Failed to spawn shell "/bin/zsh": node-pty: open_slave failed: EMFILE (errno 24, Too many open files) - slave=\'/dev/ttys003\' (shell: /bin/zsh, cwd: /tmp, arch: arm64, platform: darwin 25.0.0). If this persists, please file an issue.'
expect(addNodePtyRecoveryHint(message)).toBe(`${PTY_ALLOCATION_HINT} ${message}`)
})
it('hints unstructured openpty allocation failures', () => {
const message = 'Failed to spawn shell "/bin/bash": openpty(3) failed.'
expect(addNodePtyRecoveryHint(message)).toBe(`${PTY_ALLOCATION_HINT} ${message}`)
})
it('hints when posix_spawn reports the per-user process limit', () => {
const message =
"node-pty: posix_spawn failed: EAGAIN (errno 35, Resource temporarily unavailable) - helper='/tmp/node-pty/spawn-helper'"
expect(addNodePtyRecoveryHint(message)).toBe(
`Per-user process limit reached. Quit some agents and retry. ${message}`
)
expect(addNodePtyRecoveryHint(message)).toBe(`${TERMINAL_PROCESS_LIMIT_HINT} ${message}`)
})
it('does not duplicate an existing recovery hint', () => {
const message =
"node-pty: open_slave failed: EMFILE (errno 24, Too many open files) - slave='/dev/ttys003'"
const hinted = `${PTY_ALLOCATION_HINT} ${message}`
expect(addNodePtyRecoveryHint(hinted)).toBe(hinted)
})
it('leaves unrelated and unhinted node-pty diagnostics unchanged', () => {
+50 -5
View File
@@ -4,9 +4,45 @@ export type NodePtyDiagnostic = {
}
const NODE_PTY_DIAGNOSTIC_RE = /^node-pty: ([A-Za-z0-9_]+) failed: .*?\(errno (\d+)(?:, [^)]*)?\)/
const NODE_PTY_DIAGNOSTIC_ANYWHERE_RE =
/node-pty: ([A-Za-z0-9_]+) failed: .*?\(errno (\d+)(?:, [^)]*)?\)/
const GENERIC_PTY_ALLOCATION_RE = /\b(?:openpty|forkpty)\(3\) failed\b/i
const PTY_ALLOCATION_STEPS = new Set([
'posix_openpt',
'grantpt',
'unlockpt',
'ioctl_TIOCPTYGNAME',
'open_slave'
])
const RESOURCE_EXHAUSTION_ERRNOS = new Set([
11, // EAGAIN on Linux
12, // ENOMEM
23, // ENFILE on macOS
24, // EMFILE on macOS/Linux
35 // EAGAIN on macOS
])
const PTY_ALLOCATION_HINT = [
'Your system cannot allocate any more pty devices.',
'',
'Orca requires a pty device to launch a new terminal. This error is usually due to having too many terminal windows or terminal sessions open, either in Orca or another program.',
'',
'Free up some pty devices and try again.'
].join('\n')
const TERMINAL_PROCESS_LIMIT_HINT = [
'Your system cannot start another terminal process.',
'',
'This is usually due to having too many terminal sessions or other processes running.',
'',
'Close unused terminals or quit unused processes and try again.'
].join('\n')
export function parseNodePtyDiagnostic(message: string): NodePtyDiagnostic | null {
const match = NODE_PTY_DIAGNOSTIC_RE.exec(message)
const match =
NODE_PTY_DIAGNOSTIC_RE.exec(message) ?? NODE_PTY_DIAGNOSTIC_ANYWHERE_RE.exec(message)
if (!match) {
return null
}
@@ -21,11 +57,14 @@ export function getNodePtyRecoveryHint(diagnostic: NodePtyDiagnostic): string |
if (diagnostic.step === 'posix_spawn' && diagnostic.errno === 2) {
return "Daemon's node-pty install is gone (worktree deleted?). Restart Orca."
}
if (diagnostic.step === 'open_slave' && diagnostic.errno === 24) {
return 'Daemon hit the file-descriptor limit. Restart the daemon.'
if (
PTY_ALLOCATION_STEPS.has(diagnostic.step) &&
RESOURCE_EXHAUSTION_ERRNOS.has(diagnostic.errno)
) {
return PTY_ALLOCATION_HINT
}
if (diagnostic.step === 'posix_spawn' && diagnostic.errno === 35) {
return 'Per-user process limit reached. Quit some agents and retry.'
if (diagnostic.step === 'posix_spawn' && RESOURCE_EXHAUSTION_ERRNOS.has(diagnostic.errno)) {
return TERMINAL_PROCESS_LIMIT_HINT
}
return null
}
@@ -33,9 +72,15 @@ export function getNodePtyRecoveryHint(diagnostic: NodePtyDiagnostic): string |
export function addNodePtyRecoveryHint(message: string): string {
const diagnostic = parseNodePtyDiagnostic(message)
if (!diagnostic) {
if (GENERIC_PTY_ALLOCATION_RE.test(message) && !message.startsWith(PTY_ALLOCATION_HINT)) {
return `${PTY_ALLOCATION_HINT} ${message}`
}
return message
}
const hint = getNodePtyRecoveryHint(diagnostic)
if (hint && message.startsWith(hint)) {
return message
}
return hint ? `${hint} ${message}` : message
}