> ## Documentation Index
> Fetch the complete documentation index at: https://docs.coreweave.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Command execution

> Run commands in sandboxes with streaming, process control, and error handling.

This guide shows you how to run commands in sandboxes using the `exec()` method. Use it when you need to run shell commands, scripts, or interactive interpreters inside a sandbox and want to capture their output, stream it in real time, send input on stdin, or control how non-zero exit codes are handled. The guide is for developers who already work with the `cwsandbox` Python client.

## Run a basic command

Start with a single command that runs to completion and returns its captured output. The `exec()` method returns a `Process` handle:

```python theme={"system"}
from cwsandbox import Sandbox

with Sandbox.run() as sandbox:
    # Run a command and get the result
    result = sandbox.exec(["echo", "Hello, World!"]).result()

    print(result.stdout)      # "Hello, World!\n"
    print(result.returncode)  # 0
```

## Get results

`exec()` returns immediately so you can decide how to wait for the command. Call `.result()` on the `Process` handle to block for the output:

```python theme={"system"}
# Returns Process immediately
process = sandbox.exec(["python", "-c", "print('hello')"])

# Block for result
result = process.result()
print(result.stdout)      # "hello\n"
print(result.stderr)      # ""
print(result.returncode)  # 0

# One-liner pattern
result = sandbox.exec(["ls", "-la"]).result()
```

## Stream output

Blocking on `.result()` waits for the command to finish before you see any output. To observe progress as it happens, iterate over `process.stdout` before calling `.result()`:

```python theme={"system"}
# Returns Process immediately
process = sandbox.exec(["python", "long_script.py"])

# Stream stdout line by line
for line in process.stdout:
    print(f"[stdout] {line}", end="")

# Get final result
result = process.result()
print(f"Exit code: {result.returncode}")
```

Use streaming when you need to:

* Monitor long-running processes.
* Process output as it arrives.
* Implement progress indicators.

## Stream input to stdin

Some commands need input written to stdin while they run, such as pipelines, interactive interpreters, or tools that read until EOF. Send input to running commands by enabling stdin with `stdin=True`:

```python theme={"system"}
with Sandbox.run() as sandbox:
    process = sandbox.exec(["cat"], stdin=True)

    process.stdin.write(b"hello world\n").result()
    process.stdin.close().result()

    result = process.result()
    print(result.stdout)  # "hello world\n"
```

### StreamWriter methods

When `stdin=True`, `process.stdin` is a `StreamWriter` with three methods:

* **`write(data: bytes)`**: Write raw bytes. Returns `OperationRef[None]`.
* **`writeline(text: str)`**: Write text with a trailing newline (encodes to UTF-8). Returns `OperationRef[None]`.
* **`close()`**: Signal EOF. The system completes pending writes first. Returns `OperationRef[None]`.

When `stdin=False` (the default), `process.stdin` is `None`.

### Send multiple writes

Send data incrementally before closing:

```python theme={"system"}
process = sandbox.exec(["cat"], stdin=True)

process.stdin.writeline("line 1").result()
process.stdin.writeline("line 2").result()
process.stdin.writeline("line 3").result()
process.stdin.close().result()

result = process.result()
print(result.stdout)  # "line 1\nline 2\nline 3\n"
```

### Run interactive Python through stdin

Feed Python code to an interactive interpreter:

```python theme={"system"}
process = sandbox.exec(["python3"], stdin=True)

process.stdin.writeline("x = 40 + 2").result()
process.stdin.writeline("print(f'answer: {x}')").result()
process.stdin.close().result()

result = process.result()
print(result.stdout)  # "answer: 42\n"
```

### Combine stdin and stdout streaming

Stream output while sending input:

```python theme={"system"}
process = sandbox.exec(["cat"], stdin=True)

# Send input
process.stdin.writeline("hello").result()
process.stdin.writeline("world").result()
process.stdin.close().result()

# Stream output as it arrives
for line in process.stdout:
    print(f"[out] {line}", end="")

result = process.result()
```

### Handle EOF-dependent commands

Some commands (like `sort`) read all input before producing output. Close stdin to signal EOF:

```python theme={"system"}
process = sandbox.exec(["sort"], stdin=True)

process.stdin.writeline("banana").result()
process.stdin.writeline("apple").result()
process.stdin.writeline("cherry").result()
process.stdin.close().result()  # sort requires EOF before producing output

result = process.result()
print(result.stdout)  # "apple\nbanana\ncherry\n"
```

### Use stdin in async contexts

In async contexts, `await` each `OperationRef` directly:

```python theme={"system"}
async with Sandbox.run() as sandbox:
    process = sandbox.exec(["cat"], stdin=True)

    await process.stdin.write(b"async hello\n")
    await process.stdin.close()

    result = await process
    print(result.stdout)  # "async hello\n"
```

### When to use stdin=True compared with stdin=False

| Scenario                     | stdin   | Reason                                    |
| ---------------------------- | ------- | ----------------------------------------- |
| Run a command with arguments | `False` | Input comes from args, not stdin          |
| Pipe data into a command     | `True`  | The command reads from stdin              |
| Interactive interpreter      | `True`  | The interpreter reads commands from stdin |
| Process that reads until EOF | `True`  | Requires `close()` to signal EOF          |
| Fire-and-forget command      | `False` | No input needed                           |

## Set the working directory

By default, commands run from the sandbox's default working directory. Override that with `cwd`:

```python theme={"system"}
result = sandbox.exec(
    ["ls", "-la"],
    cwd="/app/data",
).result()
```

The path must be absolute.

## Set a timeout

To stop a command that might freeze or run longer than you expect, set a timeout with `timeout_seconds`:

```python theme={"system"}
from cwsandbox import SandboxTimeoutError

try:
    result = sandbox.exec(
        ["sleep", "60"],
        timeout_seconds=5.0,
    ).result()
except SandboxTimeoutError:
    print("Command timed out")
```

## Handle errors with check

The `check` parameter controls error behavior for non-zero exit codes:

### Default behavior with check=False

Returns the result regardless of exit code:

```python theme={"system"}
result = sandbox.exec(["false"]).result()
print(result.returncode)  # 1 (no exception)
```

### Raise on failure with check=True

Raises `SandboxExecutionError` on non-zero exit:

```python theme={"system"}
from cwsandbox import SandboxExecutionError

try:
    result = sandbox.exec(
        ["python", "-c", "raise ValueError('oops')"],
        check=True,
    ).result()
except SandboxExecutionError as e:
    print(f"Command failed: {e.exec_result.returncode}")
    print(f"stderr: {e.exec_result.stderr}")
```

## Run Python code

Sandboxes are commonly used to run Python code, either as short one-liners or as longer scripts passed inline:

```python theme={"system"}
# One-liner
result = sandbox.exec(
    ["python", "-c", "import sys; print(sys.version)"],
).result()

# Script from string
code = '''
import json
data = {"result": 42}
print(json.dumps(data))
'''
result = sandbox.exec(["python", "-c", code]).result()
output = json.loads(result.stdout)
```

## Sequential compared with parallel execution

Choose sequential execution when later commands depend on earlier ones, and parallel execution when commands are independent and you want them to run concurrently across sandboxes.

### Run commands sequentially when order matters

```python theme={"system"}
# Dependencies require sequential execution
sandbox.exec(["pip", "install", "requests"]).result()
sandbox.exec(["python", "script_using_requests.py"]).result()
```

### Run independent commands in parallel

```python theme={"system"}
# Start multiple sandboxes
sandboxes = [Sandbox.run() for _ in range(3)]

# Start commands on each
processes = [
    sb.exec(["python", "-c", f"print({i})"])
    for i, sb in enumerate(sandboxes)
]

# Collect all results
results = [p.result() for p in processes]
for r in results:
    print(r.stdout)
```

### Wait for N of M processes to complete

Use `cwsandbox.wait()` to wait for a subset of processes:

```python theme={"system"}
import cwsandbox

processes = [sb.exec(["python", "task.py"]) for sb in sandboxes]

# Wait for first 2 to complete
done, pending = cwsandbox.wait(processes, num_returns=2)

# Process completed ones immediately
for p in done:
    print(p.result().stdout)

# Wait for remaining
for p in pending:
    print(p.result().stdout)
```

## Control processes

When a command keeps running after the `exec()` call returns, you can check on it without blocking or wait for it to finish on your own terms. The `Process` handle provides methods for monitoring and control:

```python theme={"system"}
process = sandbox.exec(["python", "server.py"])

# Check if running (non-blocking)
if process.poll() is None:
    print("Still running")

# Wait for completion
exit_code = process.wait()
print(f"Exited with code: {exit_code}")
```
