Skip to content
Draft
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
49 changes: 48 additions & 1 deletion packages/cli/src/terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,39 @@ function getStdoutSize() {
}
}

function isRetryableError(err: unknown): boolean {
// Retry on SDK TimeoutError
if (err instanceof (e2b as any).TimeoutError) return true

// Some environments throw AbortError for aborted/timeout fetches
if (err && typeof err === 'object' && (err as any).name === 'AbortError')
return true

// Network/system-level transient errors commonly exposed via code property
const code = (err as any)?.code ?? (err as any)?.cause?.code
const retryableCodes = new Set([
'ECONNRESET',
'ECONNREFUSED',
'ECONNABORTED',
'EPIPE',
'ETIMEDOUT',
'ENOTFOUND',
'EAI_AGAIN',
'EHOSTUNREACH',
'EADDRINUSE',
])
if (typeof code === 'string' && retryableCodes.has(code)) return true

// Undici/Fetch may surface as TypeError: fetch failed with nested cause
if ((err as any) instanceof TypeError) {
const msg = String((err as any).message || '').toLowerCase()
if (msg.includes('fetch failed') || msg.includes('network error'))
return true
}

return false
}

export async function spawnConnectedTerminal(sandbox: e2b.Sandbox) {
// Clear local terminal emulator before starting terminal
// process.stdout.write('\x1b[2J\x1b[0f')
Expand All @@ -26,7 +59,21 @@ export async function spawnConnectedTerminal(sandbox: e2b.Sandbox) {

const inputQueue = new BatchedQueue<Buffer>(async (batch) => {
const combined = Buffer.concat(batch)
await sandbox.pty.sendInput(terminalSession.pid, combined)

const maxRetries = 3
let retry = 0
do {
try {
await sandbox.pty.sendInput(terminalSession.pid, combined)
break
} catch (err) {
if (!isRetryableError(err)) {
// Do not retry on errors that come with valid HTTP/gRPC responses
throw err
}
retry++
}
} while (retry < maxRetries)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Silent Failures Drop User Input

After exhausting all retry attempts with retryable errors, the retry loop exits silently without throwing an error or logging the failure. This causes user input to be silently dropped when sandbox.pty.sendInput fails repeatedly, with no indication to the user that their keystrokes weren't sent to the terminal.

Fix in Cursor Fix in Web

}, FLUSH_INPUT_INTERVAL_MS)

const resizeListener = process.stdout.on('resize', () =>
Expand Down
Loading