# Interactive terminal (PTY) - Documentation

> For the complete documentation index, see [llms.txt](/llms.txt). Markdown is available with `Accept: text/markdown` and `.md` URL variants.

Source: /docs/guides/sandbox-interactive-terminal

# Interactive terminal (PTY)

The PTY, or pseudo-terminal, module enables interactive terminal sessions inside the sandbox with real-time, bidirectional communication.
A PTY session supports real-time streaming, delivering terminal output continuously through callbacks as it is produced, and provides bidirectional input, allowing data to be sent while the session is still running. It also offers an interactive shell experience with full terminal behavior, including ANSI colors and escape sequences, and supports session persistence, so a running session can be detached and reconnected later.

##

[​](#create-a-pty-session)

Create a PTY session

You can use `sandbox.pty.create()` to start an interactive bash shell.

JavaScript & TypeScript

Python

```
import { Sandbox } from 'novita-sandbox/code-interpreter'

const sandbox = await Sandbox.create()

const terminal = await sandbox.pty.create({
cols: 80, // Terminal width in characters
rows: 24, // Terminal height in characters
onData: (data) => {
// Called whenever terminal outputs data
process.stdout.write(data)
},
envs: { MY_VAR: 'hello' }, // Optional environment variables
cwd: '/home/user', // Optional working directory
user: 'root', // Optional user to run as
})

// terminal.pid contains the process ID
console.log('Terminal PID:', terminal.pid)
```

```
import threading

from novita_sandbox.code_interpreter import Sandbox, PtySize

sandbox = Sandbox.create()

terminal = sandbox.pty.create(
size=PtySize(rows=24, cols=80), # PtySize is (rows, cols)
envs={'MY_VAR': 'hello'}, # Optional environment variables
cwd='/home/user', # Optional working directory
user='root', # Optional user to run as
)

# terminal.pid contains the process ID
print('Terminal PID:', terminal.pid)

# The Python SDK has no on_data param. Output is streamed via wait(on_pty=...),
# which blocks, so run it in a background thread.
threading.Thread(
target=lambda: terminal.wait(on_pty=lambda data: print(data.decode(), end='')),
daemon=True,
).start()
```

The PTY launches an interactive bash shell with `TERM=xterm-256color`, so ANSI colors and escape sequences work as expected.

##

[​](#timeout)

Timeout

The timeout setting is configurable and determines how long the PTY session remains active. You can keep a PTY session open indefinitely by setting `timeoutMs: 0` in JavaScript or `timeout=0` in Python. The session uses a 60-second timeout by default.

JavaScript & TypeScript

Python

```
import { Sandbox } from 'novita-sandbox/code-interpreter'

const sandbox = await Sandbox.create()

const terminal = await sandbox.pty.create({
cols: 80,
rows: 24,
onData: (data) => process.stdout.write(data),
timeoutMs: 0, // Keep the session open indefinitely
})
```

```
from novita_sandbox.code_interpreter import Sandbox, PtySize

sandbox = Sandbox.create()

terminal = sandbox.pty.create(
size=PtySize(rows=24, cols=80),
timeout=0, # Keep the session open indefinitely
)
```

##

[​](#send-input-to-pty)

Send input to PTY

You can use `sendInput()` in JavaScript or `send_stdin()` in Python to send data to the terminal.
In JavaScript, `sendInput()` returns a Promise, and any terminal output is delivered through the `onData` callback rather than returned directly.
In Python, `send_stdin()` completes synchronously, and any terminal output is delivered through the `on_pty` callback passed to `wait()`.

JavaScript & TypeScript

Python

```
import { Sandbox } from 'novita-sandbox/code-interpreter'

const sandbox = await Sandbox.create()

const terminal = await sandbox.pty.create({
cols: 80,
rows: 24,
onData: (data) => process.stdout.write(data),
})

// Send a command (don't forget the newline!)
await sandbox.pty.sendInput(
terminal.pid,
new TextEncoder().encode('echo "Hello from PTY"\n')
)
```

```
import threading

from novita_sandbox.code_interpreter import Sandbox, PtySize

sandbox = Sandbox.create()

terminal = sandbox.pty.create(size=PtySize(rows=24, cols=80))

# Stream output in a background thread (Python uses wait(on_pty=...))
threading.Thread(
target=lambda: terminal.wait(on_pty=lambda data: print(data.decode(), end='')),
daemon=True,
).start()

# Send a command as bytes (b'...' is Python's byte string syntax)
# Don't forget the newline!
sandbox.pty.send_stdin(terminal.pid, b'echo "Hello from PTY"\n')
```

##

[​](#resize-the-terminal)

Resize the terminal

You can use `resize()` to notify the PTY when the user changes the terminal window size.
The cols and rows values represent the terminal dimensions in characters rather than pixels.

JavaScript & TypeScript

Python

```
import { Sandbox } from 'novita-sandbox/code-interpreter'

const sandbox = await Sandbox.create()

const terminal = await sandbox.pty.create({
cols: 80,
rows: 24,
onData: (data) => process.stdout.write(data),
})

// Resize to new dimensions (in characters)
await sandbox.pty.resize(terminal.pid, {
cols: 120,
rows: 40,
})
```

```
from novita_sandbox.code_interpreter import Sandbox, PtySize

sandbox = Sandbox.create()

terminal = sandbox.pty.create(size=PtySize(rows=24, cols=80))

# Resize to new dimensions (in characters)
sandbox.pty.resize(terminal.pid, PtySize(rows=40, cols=120))
```

##

[​](#disconnect-and-reconnect)

Disconnect and reconnect

A PTY session can remain active even after the client disconnects. You can detach from the session and reconnect to it again later with a new data handler.
This can be used to recover from network interruptions, support terminal access from multiple clients, and preserve session state across reconnects.

JavaScript & TypeScript

Python

```
import { Sandbox } from 'novita-sandbox/code-interpreter'

const sandbox = await Sandbox.create()

// Create a PTY session
const terminal = await sandbox.pty.create({
cols: 80,
rows: 24,
onData: (data) => console.log('Handler 1:', new TextDecoder().decode(data)),
})

const pid = terminal.pid

// Send a command
await sandbox.pty.sendInput(pid, new TextEncoder().encode('echo hello\n'))

// Disconnect - PTY keeps running in the background
await terminal.disconnect()

// Later: reconnect with a new data handler
const reconnected = await sandbox.pty.connect(pid, {
onData: (data) => console.log('Handler 2:', new TextDecoder().decode(data)),
})

// Continue using the session
await sandbox.pty.sendInput(pid, new TextEncoder().encode('echo world\n'))

// Wait for the terminal to exit
await reconnected.wait()
```

```
import threading
import time

from novita_sandbox.code_interpreter import Sandbox, PtySize

sandbox = Sandbox.create()

# Create a PTY session
terminal = sandbox.pty.create(size=PtySize(rows=24, cols=80))
pid = terminal.pid

# Send a command
sandbox.pty.send_stdin(pid, b'echo hello\n')
time.sleep(0.5)

# Disconnect - PTY keeps running in the background.
# Don't disconnect while a wait() is iterating the same handle.
terminal.disconnect()

# Later: reconnect with a new handle and stream its output
reconnected = sandbox.pty.connect(pid)
threading.Thread(
target=lambda: reconnected.wait(on_pty=lambda data: print('Handler 2:', data.decode())),
daemon=True,
).start()

# Continue using the session
sandbox.pty.send_stdin(pid, b'echo world\n')
time.sleep(1.5)
```

##

[​](#kill-the-pty)

Kill the PTY

You can use `kill()` to terminate the PTY session.

JavaScript & TypeScript

Python

```
import { Sandbox } from 'novita-sandbox/code-interpreter'

const sandbox = await Sandbox.create()

const terminal = await sandbox.pty.create({
cols: 80,
rows: 24,
onData: (data) => process.stdout.write(data),
})

// Kill the PTY
const killed = await sandbox.pty.kill(terminal.pid)
console.log('Killed:', killed) // true if successful

// Or use the handle method
// await terminal.kill()
```

```
from novita_sandbox.code_interpreter import Sandbox, PtySize

sandbox = Sandbox.create()

terminal = sandbox.pty.create(size=PtySize(rows=24, cols=80))

# Kill the PTY
killed = sandbox.pty.kill(terminal.pid)
print('Killed:', killed) # True if successful

# Or use the handle method
# terminal.kill()
```

##

[​](#wait-for-pty-to-exit)

Wait for PTY to exit

You can use `wait()` to block until the terminal session ends, for example when the user types `exit`.

JavaScript & TypeScript

Python

```
import { Sandbox } from 'novita-sandbox/code-interpreter'

const sandbox = await Sandbox.create()

const terminal = await sandbox.pty.create({
cols: 80,
rows: 24,
onData: (data) => process.stdout.write(data),
})

// Send exit command
await sandbox.pty.sendInput(terminal.pid, new TextEncoder().encode('exit\n'))

// Wait for the terminal to exit
const result = await terminal.wait()
console.log('Exit code:', result.exitCode)
```

```
from novita_sandbox.code_interpreter import Sandbox, PtySize

sandbox = Sandbox.create()

terminal = sandbox.pty.create(size=PtySize(rows=24, cols=80))

# Send exit command
sandbox.pty.send_stdin(terminal.pid, b'exit\n')

# wait() blocks until the terminal exits; pass on_pty to stream output
result = terminal.wait(on_pty=lambda data: print(data.decode(), end=''))
print('Exit code:', result.exit_code)
```

##

[​](#interactive-terminal-ssh-like)

Interactive terminal (SSH-like)

You can use the same `sandbox.pty` API described above to create a fully interactive terminal such as SSH by handling raw mode, stdin forwarding, and terminal resize events.

Last modified on June 4, 2026
