# 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, and a long stream picks itself up again from the right byte.** When a stream runs past the server's time limit, the SDK resumes it from a cursor, so no line is lost or printed twice. A command in a sandbox already in use answered in a median 105 ms, measured on 24 September 2026 ([speed](/docs/speed)), so the first line of a build or a test run reaches your terminal almost at once. ## Print output as it arrives The callbacks get each piece of `stdout` and `stderr` as text, and 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 sandbox exec` streams by default and exits with the command's own exit code, 124 when it timed out, so `set -e` scripts behave: ```bash id=$(runtime sandbox create) runtime sandbox exec "${id}" -- python3 -c 'print(6 * 7)' runtime sandbox stop "${id}" ``` ## Read the events one by one When you want to tell the streams apart or react to the exit, iterate the events. Each 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` answers NDJSON, one event a line, and 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 seconds by default, 24 hours at most; a timeout is a result | | `runtime sandbox exec --json` | One result, at most 64 KiB of each stream, with the truncation flags | Output costs nothing on its own. The sandbox is billed for the CPU it uses, $0.025 per active vCPU-hour, and its memory, $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 send the output to a file (`cmd > /workspace/out.log`) and read it with `sbx.files.readText`. - **Treating a timeout as an exception.** It comes back as `timedOut: true` (`timed_out` in Python) with the output so far. Add `check: true` to throw `CommandError` on a non-zero exit instead. - **A reader that stops reading.** The sandbox holds the latest 1 MiB of a command's output. A consumer that falls further behind loses the oldest part, 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), whose output you follow 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 colours and prompts. - [Run untrusted LLM code](/use-cases/run-untrusted-llm-code), where an agent reads output as it comes. - The full exec reference in [JavaScript](/docs/javascript#run-commands) and [Python](/docs/python#run-commands). Facts on this page were checked on 25 September 2026.