Skip to main content
SimpleBox.exec() moves the execute step wholesale into the sandbox, so an agent’s rm -rf or unknown package install leaves the host untouched. Anything present in the image runs: python, node, go run, bash, your own binary.

Quick Example (minimal happy path)

The snippet below runs as-is. It starts an Alpine box, runs one command, and reads the result back.
To run a different language, change the program name. For example, have an agent run a piece of Python it generated:
Node version (equivalent happy path):

Common Usage

Set environment variables, working directory, and run user

Environment variables use a dict (not a list) and are injected for this exec call:

Streaming / incremental output collection

SimpleBox.exec already collects stdout and stderr concurrently internally (to avoid the deadlock of a full pipe buffer) and returns them together when done. If you want truly line-by-line streaming (consume output while it runs), drill down to the lower-level Box (the SimpleBox internal handle box._box) to get the async iterator streams of an Execution:
Note: box._box is an SDK-internal handle; the leading underscore signals “not public, but stable to use”. For most cases, await box.exec(...) and its aggregated result are enough; drill down to Execution only when you need to consume output as it runs (long-running task progress, log following).

Timeout control

SimpleBox.exec’s timeout parameter is timeout (float, seconds). A timeout does not raise a Python exception; the process is killed with SIGTERM and returns a negative exit code (-15, that is -SIGTERM) — check exit_code (negative / non-zero means it was killed). On this path, error_message is None; to detect a timeout, inspect exit_code:
Naming reminder: the wrapper-layer SimpleBox.exec uses timeout (float); the lower-level native Box.exec uses timeout_secs (see the streaming example above); CodeBox.run uses timeout (int).

Harden the box for untrusted code

The microVM boundary is always on. Two further knobs narrow what the code inside can reach — tighten the OS-level sandbox around the VM, and restrict where it can talk to.
security= is not a top-level keyword — passing it directly raises TypeError: BoxOptions.__new__() got an unexpected keyword argument 'security'. It must be wrapped in advanced=AdvancedBoxOptions(security=...).
Details: Secrets and hardening and Network access.

Parameters and Returns

SimpleBox.exec(cmd, *args, env=None, user=None, timeout=None, cwd=None)

Source: sdks/python/boxlite/simplebox.py:175 Returns ExecResult (a dataclass, sdks/python/boxlite/exec.py:14):

Node equivalent: SimpleBox.exec(...)

Source: sdks/node/lib/simplebox.ts. Three overloads: Returns ExecResult = { exitCode: number; stdout: string; stderr: string } (camelCase exitCode). Node’s env is also an object, Record<string,string>, not an array.

Troubleshooting

Passing env as a list -> AttributeError / wrong behavior

SimpleBox.exec(env=...) expects a dict; internally it converts via env.items() (simplebox.py:220). Passing a list errors at the .items() call.
Note the distinction: the SimpleBox(...) constructor’s box-level env= takes a list of tuples (for example env=[("USER","alice")]); whereas exec(env=...) takes a dict. The two layers differ — don’t mix them up.

A command failed but raised nothing

A non-zero exit code from exec does not raise — this is by design (so an agent can self-check and decide based on it). Always check exit_code:
Node behaves the same: await box.exec("false") returns { exitCode: 1 } and does not reject.

Missing command / image pull failure -> standard RuntimeError / bare Error (not BoxliteError)

A missing command or an image pull failure raises a standard RuntimeError (Python) / bare Error (Node), not BoxliteError. See Error Handling.

Writing to /tmp, /dev/shm (tmpfs) and then not finding it

This is unrelated to exec but often shows up alongside it: copy_in to a tmpfs mount point (the same limitation as docker cp) may not be readable afterward. To get a file into /tmp, pipe a tar through exec, or write to a non-tmpfs path. See Moving files without a mount.

Don’t pack arguments into one string

exec("ls -l /") looks for "ls -l /" as a single program name and will fail. Split it into exec("ls", "-l", "/"), or go through a shell explicitly: exec("sh", "-c", "ls -l /").

Environment constraint: hardware virtualization required

Each BoxLite box is a microVM and needs underlying virtualization:
  • Linux: requires KVM (/dev/kvm available); WSL2 needs KVM and the user in the kvm group.
  • macOS arm64: uses Apple’s Hypervisor.framework and needs no /dev/kvm. macOS Intel is not supported.
  • Environments without virtualization (most CI containers) -> the box fails to start and raises a standard RuntimeError (the process does not crash; it can be caught).