# How to stream command output from a sandbox Pass `onStdout` to `exec` (`on_stdout` in Python), or loop over `execStream()`. Each chunk arrives as the command prints it. **On Runtime, a streamed command has no output limit. A long stream resumes automatically from the correct byte.** When a stream runs past the server's time limit, the SDK resumes it from a cursor. No line is lost or printed twice. The first line of a build or a test run reaches your terminal almost immediately. ## Print output as it arrives Callbacks receive each piece of `stdout` and `stderr` as text. The result still holds everything the command printed: ```ts import { Sandbox } from "withruntime"; await using sbx = await Sandbox.create(); const result = await sbx.exec("for i in 1 2 3; do echo line $i; sleep 1; done", { onStdout: (text) => process.stdout.write(text), onStderr: (text) => process.stderr.write(text), }); console.log("exit", result.exitCode, result.stdoutTruncated); ``` ```python import sys from withruntime import Sandbox with Sandbox.create() as sbx: result = sbx.exec("for i in 1 2 3; do echo line $i; sleep 1; done", on_stdout=sys.stdout.write, on_stderr=sys.stderr.write) print("exit", result.exit_code, result.stdout_truncated) ``` From a terminal, `runtime sandboxes exec` streams output by default. It exits with the command's own exit code, or with 124 when the command times out. Scripts that use `set -e` behave as expected: ```bash id=$(runtime sandboxes create) runtime sandboxes exec "${id}" -- python3 -c 'print(sum([125, 250, 375]))' runtime sandboxes stop "${id}" ``` ## Read the events one by one To tell the streams apart or act on the exit, iterate over the events. Each event has a `type`: `start`, `stdout`, `stderr` or `exit`. ```ts import { Sandbox } from "withruntime"; await using sbx = await Sandbox.create(); for await (const event of sbx.execStream("npm --version")) { if (event.type === "stdout") process.stdout.write(event.data); if (event.type === "stderr") process.stderr.write(event.data); if (event.type === "exit") console.log("exit", event.exitCode); } ``` ```python from withruntime import Sandbox with Sandbox.create() as sbx: for event in sbx.exec_stream("npm --version"): if event["type"] == "stdout": print(event["data"], end="") elif event["type"] == "exit": print("exit", event["exitCode"]) ``` Over plain HTTP, `POST /v1/sandboxes/{id}:exec` with `"stream": true` returns NDJSON, with one event per line. A `continue` event carries the cursor to resume from ([commands and processes](/docs/api#commands-and-processes)). ## Output limits | Case | What you get back | | ------------------------------------------ | ----------------------------------------------------------------------------------- | | `exec` with no callback, timeout up to 60s | At most 64 KiB of `stdout` and 64 KiB of `stderr` in the result | | `exec` with `onStdout` or `onStderr` | Streams; the result keeps everything the command printed | | `exec` with `timeoutMs` over 60,000 | Streams by itself, as if you passed a callback | | `execStream()` | Every event, with no output limit | | A reader far behind | The sandbox keeps the latest 1 MiB; a `truncated` event says so | | Timeout | 60 s by default, up to 24 h when streamed. A timeout returns a result, not an error | | `runtime sandboxes exec --json` | One result, at most 64 KiB of each stream, with the truncation flags | Output has no charge of its own. The sandbox is billed for the CPU it uses, at $0.025 per active vCPU-hour, and for its memory, at $0.0075 per GiB-hour ([pricing](/docs/pricing)). ## Mistakes to avoid - **Reading a big log from the plain result.** Past 64 KiB, the rest is dropped and `stdoutTruncated` is `true`. Check the flag, add a callback, or write the output to a file (`cmd > /workspace/out.log`) and read it with `sbx.files.readText`. - **Treating a timeout as an exception.** The result returns `timedOut: true` (`timed_out` in Python) with the output so far. To throw `CommandError` on a non-zero exit instead, add `check: true`. - **A reader that stops reading.** A slow reader is fine. The command waits for it. The sandbox holds the latest 1 MiB of a command's output. If a consumer is away for more than 10 seconds while more output arrives, it loses the oldest part. It then gets a `truncated` event, and both truncation flags are set. Keep the loop reading, or write to a file. - **Streaming a server.** `exec` waits for the command to end. A dev server or a watcher belongs in a [background process](/how-to/run-a-background-process). Follow its output separately. - **A secret in the command line.** Pass it in `env`. It is never echoed back, and journals record a hash, not the value. ## Related - [Run a background process](/how-to/run-a-background-process) and follow its logs after you disconnect. - [Open an interactive terminal](/how-to/open-an-interactive-terminal) for colors and prompts. - [Run untrusted LLM code](/use-cases/run-untrusted-llm-code), where an agent reads output as it is produced. - The full exec reference in [JavaScript](/docs/javascript#run-commands) and [Python](/docs/python#run-commands). Facts on this page were checked on 26 October 2026.