Skip to main content

Overview

Shell commands and Python code share the same filesystem:
Environment variables don’t cross between shell and Python: sandbox.commands.run("export FOO=bar") is not visible in sandbox.code.run(...). Use the filesystem to share data between the two.

Return values

sandbox.commands.run(cmd)

Returns a command result:

sandbox.code.run(code)

Returns an execution result:

Timeouts

Both commands.run() and code.run() accept a timeout parameter, in seconds. When the timeout is reached, the guest work is interrupted and the call returns instead of waiting for it to finish.
A shell command that hits its timeout returns with exit_code 124:

Interrupting a command

A command no longer has to run to its timeout. interrupt() stops whatever is in the foreground, and the call that was waiting returns with exit code 130:
The command really stops. This is not the caller walking away while the guest keeps working, so the sandbox is yours again immediately. Ctrl-C works too. It used to do nothing while a command was running, because KeyboardInterrupt could not land on a thread blocked inside the emulator. It now reaches the command, which makes an interactive REPL behave the way you would expect.
code.run() cannot be interrupted yet, so use timeout there.There is no signal parameter, and none is needed: run() blocks, so Ctrl-C reaches it directly and timeout already covers deadlines. The TypeScript SDK needs an AbortSignal because its call returns a promise and the caller keeps going.A command that ignores the interrupt runs to its own deadline and is recovered the same way a timeout is.

Streaming output

commands.run() hands back everything at once, when the command ends. For a build or a test run that is a long wait followed by a wall of text, so it also takes on_stdout and on_stderr, called with each piece of output as it arrives:
The return value does not change. What the callbacks receive concatenates to exactly the stdout you get without them, so you can add one to existing code and nothing else moves. Chunks arrive as the guest produces output, not on a fixed schedule. A command printing two hundred lines with no pauses delivers them in one chunk; one printing a line a second delivers a chunk per line. A command that finishes quickly delivers a single chunk, which is indistinguishable from not streaming, and that is correct: there was nothing to stream. Streaming composes with interrupts. The chunks that arrived before the stop are kept, and the result still carries exit code 130.
code.run() does not stream, and does not accept on_stdout. Shell commands are what tend to run long enough to be worth watching.

Sandbox.create() parameters

snapshot

The snapshot to boot from. Defaults to alpine. See Snapshots for the full catalog.

mounts

Mount host directories into the sandbox. Paths are read-only by default; append :rw for read-write access.