Skip to main content
Under the hood it allocates a PTY for the command and forwards your local stdin, stdout, and stderr bidirectionally.
InteractiveBox — the wrapper that handles raw mode and bidirectional forwarding for you — ships in the Python and Node SDKs. PTY itself works from every SDK: call exec(..., tty=True) to get an Execution and forward stdin/stdout yourself (see “Lower-level PTY” at the end).
  • You must run this in a real terminal. Interactive forwarding is enabled only when stdin is a TTY; in CI, pipes, Jupyter, or an IDE’s “Run” button, stdin is usually not a TTY, so keyboard forwarding is disabled automatically (see the tty parameter under Parameters and Returns).

Quick Example (happy path)

Python

Save the whole block below as interactive.py, run python interactive.py in a terminal, and you drop straight into the box’s shell. Type exit or press Ctrl-D to quit.

Node

Save as interactive.mjs (@boxlite-ai/boxlite is ESM-only) and run node interactive.mjs in a terminal.

Parameters and Returns

InteractiveBox(...) constructor parameters (Python)

Source: sdks/python/boxlite/interactivebox.py:43. InteractiveBox extends SimpleBox; keyword arguments not listed (such as working_dir, volumes, ports) pass through **kwargs to SimpleBox.
Leaving memory_mib / cpus unset means “let the runtime decide”. Compute resources → Defaults is the single source of truth for those numbers.
Naming difference: in SimpleBox.exec(env=...) the env is a dict; but InteractiveBox’s constructor env is fed directly to native Box.exec, so it must be a list[tuple] (see _start_interactive_shell in interactivebox.py).

InteractiveBoxOptions (Node)

Source: sdks/node/lib/interactivebox.ts. Extends SimpleBoxOptions; the constructor requires an options object.

Methods and return values

Context-manager type: InteractiveBox is an async context manager (async with). The runtime handle Boxlite is a synchronous with — don’t mix them up.

Troubleshooting

No interaction, keyboard does nothing (running in an IDE/CI/pipe)

Symptom: the script runs, but keystrokes get no response, or it blocks at wait(). Cause: interactive forwarding is enabled only when stdin is a TTY. tty=None (Python) / undefined (Node) auto-detects; under an IDE “Run” button, CI, python x.py < file, or | pipe, stdin is not a TTY, so forwarding is off. Fix: run it in a real terminal; if you must force forwarding, pass tty=True (Python) / tty: true (Node) explicitly. Note: when you force tty=True in a non-TTY environment, Python calls termios.tcgetattr / tty.setraw on stdin in __aenter__ and raises termios.error. The exact errno varies by platform / stdin type (commonly (25, 'Inappropriate ioctl for device') under a pipe, and (19, 'Operation not supported by device') on macOS without a TTY). Whatever the errno, it is the same termios.error class; catch it with except termios.error (or a broad except Exception).

env passed with the wrong type

InteractiveBox’s env is passed to the lower-level Box.exec, so it must be list[tuple[str, str]]:
(Node is the opposite: InteractiveBoxOptions.env uses a Record<string, string> object.)

Startup failure / no virtualization

BoxLite requires hardware virtualization: Linux needs KVM (/dev/kvm available, user in the kvm group); macOS uses Apple’s Hypervisor.framework (no KVM needed); WSL2 needs KVM enabled. Without virtualization, the box fails to start — the exception can be caught with try/except (Python) / try/catch (Node), and the process does not crash. macOS Intel is not supported.

Image pull failure raises RuntimeError, not BoxliteError

When an image pull fails due to a network hiccup, Python raises the built-in RuntimeError (Node a bare Error), not a BoxliteError subclass. Catch it with a broad except Exception / catch (err) and retry; don’t catch only BoxliteError.

Shell does not exist

If shell points to a path that the image does not have (such as /bin/bash on plain alpine), it raises a RuntimeError when entering async with (the __aenter__ that launches the PTY shell), with a message such as:
The error is raised during startup — wrap the async with in try/except RuntimeError to catch it. Make sure the target image has that shell (alpine has /bin/sh by default, but not bash).

Lower-level PTY (C / Go / Rust, no high-level wrapper)

From C / Go / Rust, drive the PTY through exec directly and forward I/O yourself:
  • Rust: BoxCommand::new(shell).tty(true), then box.exec(cmd) to get an Execution, and forward from execution.stdin()/stdout()/stderr() (all &mut); execution.resize_tty(rows, cols) resizes the window. The execution method is exec (there is no litebox.run).
  • C: BoxliteCommand.tty = 1, boxlite_box_exec(handle, &cmd, &execution, &error) to get the execution; output via the boxlite_execution_on_stdout / on_stderr callbacks, stdin via boxlite_execution_stdin_write, window size via boxlite_execution_tty_resize. This is a post-and-drain model — you must call boxlite_runtime_drain in a loop to drive the callbacks.
  • Go: box.StartExecution(...) with ExecutionOptions{TTY: true, OnStdout: ..., OnStderr: ...}, writing stdin yourself.
On these paths PTY allocation is real; they lack the convenience layer of automatic raw-mode plus bidirectional forwarding.