# Executing Commands in Sandboxes — Execute commands and read their output with the Render CLI or SDK.


> *Render Sandboxes is in public beta.*
>
> APIs, defaults, and limits might change during the beta period.

After you [create a sandbox](sandboxes-creating), you can start executing commands inside it with the Render CLI or SDK. Commands execute in the sandbox's isolated environment and stream their output back to your client.

- Each command starts in a new shell in your sandbox's home directory.
- Each command inherits the [environment variables](sandboxes-creating#environment-variables) provided when the sandbox was created.
- Changes to the working directory or shell variables are not preserved across commands.

## Basic example

The following example executes `echo hello` and displays its output and exit code:

**Tab: CLI**

```shell
render sandboxes exec sbx-abc123 -- echo hello
```

Provide the command after the `--` separator so its flags are passed to the command instead of the Render CLI. For example:

```shell
render sandboxes exec sbx-abc123 -- ls -la
```

The CLI streams output directly to your terminal and exits with the command's exit code. To display that code in your shell, use `echo $?` immediately after the command.

**Tab: TypeScript**

```typescript
import { Render } from '@renderinc/sdk'

const sandboxes = new Render().sandboxes
const sandboxId = 'sbx-abc123'

const events = await sandboxes.exec(sandboxId, 'echo hello')

for await (const event of events) {
  if (event.type === 'output') {
    const stream = event.stream === 'stderr' ? process.stderr : process.stdout
    stream.write(event.data)
  } else if (event.type === 'exit') {
    console.log(`Exit code: ${event.exit_code}`)
  }
}
```

Call [`sandboxes.exec`](sandboxes-sdk-typescript#exec) with the sandbox's ID and a command string. Await the method to obtain an async iterator, then use `for await` to read events as they arrive.

**Tab: Python (sync)**

```python
import sys
from render import Render
from render.sandbox import SandboxExecExit, SandboxExecOutput

sandboxes = Render().sandboxes
sandbox_id = "sbx-abc123"

for event in sandboxes.exec(sandbox_id, "echo hello"):
    if isinstance(event, SandboxExecOutput):
        stream = sys.stderr if event.stream == "stderr" else sys.stdout
        print(event.data, end="", file=stream, flush=True)
    elif isinstance(event, SandboxExecExit):
        print(f"Exit code: {event.exit_code}")
```

Call [`sandboxes.exec`](sandboxes-sdk-python#exec) with the sandbox's ID and a command string. Use a `for` loop to read events as they arrive.

**Tab: Python (async)**

```python
import asyncio
import sys
from render import RenderAsync
from render.sandbox import SandboxExecExit, SandboxExecOutput

async def main():
    sandboxes = RenderAsync().sandboxes
    sandbox_id = "sbx-abc123"

    async for event in sandboxes.exec(sandbox_id, "echo hello"):
        if isinstance(event, SandboxExecOutput):
            stream = sys.stderr if event.stream == "stderr" else sys.stdout
            print(event.data, end="", file=stream, flush=True)
        elif isinstance(event, SandboxExecExit):
            print(f"Exit code: {event.exit_code}")

asyncio.run(main())
```

Call [`sandboxes.exec`](sandboxes-sdk-python#exec) with the sandbox's ID and a command string. Use `async for` to read events as they arrive. The method returns an async iterator directly, so you don't `await` the method call.

## Streaming events

When you execute a command with either SDK, the `exec` method provides a stream of events that you read in a loop, as shown above:

**Tab: TypeScript**

An *output event* contains a chunk of text in `data`. Its `stream` is either `stdout` or `stderr`:

```json
{
  "type": "output",
  "stream": "stdout",
  "data": "hello\n"
}
```

An *exit event* arrives when the command finishes and contains its exit code:

```json
{
  "type": "exit",
  "exit_code": 0
}
```

**Tab: Python**

An *output event* is a `SandboxExecOutput` object containing a chunk of text in `data`. Its `stream` is either `stdout` or `stderr`:

```python
SandboxExecOutput(stream="stdout", data="hello\n")
```

An *exit event* arrives when the command finishes. It is a `SandboxExecExit` object containing the command's exit code:

```python
SandboxExecExit(exit_code=0)
```

Output arrives in chunks that can contain part of a line or multiple lines. Write each chunk as received to preserve the original output.

## Checking for command failure

An exit event's `exit_code` matches the exit code of the executed command. By convention, `0` indicates success and a nonzero value indicates failure.

*In the Render SDK, a nonzero exit code does not throw or raise an error.* Check the exit event and handle it in your own code:

**Tab: CLI**

```shell
render sandboxes exec sbx-abc123 -- sh -c 'exit 3'
echo $? # 3
```

The CLI's exit code lets scripts and CI jobs check whether the command succeeded.

**Tab: TypeScript**

```typescript
const events = await sandboxes.exec(sandboxId, 'exit 3')

for await (const event of events) {
  if (event.type === 'output') {
    const stream = event.stream === 'stderr' ? process.stderr : process.stdout
    stream.write(event.data)
  } else if (event.type === 'exit' && event.exit_code !== 0) {
    throw new Error(`Command failed with exit code ${event.exit_code}`)
  }
}
```

**Tab: Python (sync)**

```python
for event in sandboxes.exec(sandbox_id, "exit 3"):
    if isinstance(event, SandboxExecOutput):
        stream = sys.stderr if event.stream == "stderr" else sys.stdout
        print(event.data, end="", file=stream, flush=True)
    elif isinstance(event, SandboxExecExit) and event.exit_code != 0:
        raise RuntimeError(f"Command failed with exit code {event.exit_code}")
```

**Tab: Python (async)**

```python
async for event in sandboxes.exec(sandbox_id, "exit 3"):
    if isinstance(event, SandboxExecOutput):
        stream = sys.stderr if event.stream == "stderr" else sys.stdout
        print(event.data, end="", file=stream, flush=True)
    elif isinstance(event, SandboxExecExit) and event.exit_code != 0:
        raise RuntimeError(f"Command failed with exit code {event.exit_code}")
```

Errors connecting to the sandbox or reading its output are reported separately. For all error types, see the [TypeScript](sandboxes-sdk-typescript#error-types) and [Python](sandboxes-sdk-python#error-types) SDK references.

## Executing a sequence of commands

To execute a sequence in the same shell, combine commands with `&&`. Each command executes only if the previous one succeeds:

**Tab: CLI**

```shell
render sandboxes exec sbx-abc123 -- sh -c \
  'mkdir -p /tmp/example && cd /tmp/example && echo hello > message.txt && cat message.txt'
```

Use `sh -c` to interpret operators such as `&&`, pipes, and redirection inside the sandbox. The surrounding single quotes prevent your local shell from interpreting them.

**Tab: TypeScript**

```typescript
const events = await sandboxes.exec(
  sandboxId,
  'mkdir -p /tmp/example && cd /tmp/example && echo hello > message.txt && cat message.txt'
)

for await (const event of events) {
  console.log(event)
}
```

**Tab: Python (sync)**

```python
events = sandboxes.exec(
    sandbox_id,
    "mkdir -p /tmp/example && cd /tmp/example && echo hello > message.txt && cat message.txt",
)

for event in events:
    print(event)
```

**Tab: Python (async)**

```python
events = sandboxes.exec(
    sandbox_id,
    "mkdir -p /tmp/example && cd /tmp/example && echo hello > message.txt && cat message.txt",
)

async for event in events:
    print(event)
```

This example creates a directory, changes into it, writes a file, and displays the file's contents. For transferring files between your client and a sandbox, see [Managing Sandbox Files](sandboxes-files).

## Stopping execution

*Disconnecting your client does not stop an executing command inside the sandbox.* This includes interrupting the CLI or canceling a stream request.

To stop all processes and permanently remove the sandbox's filesystem, [terminate the sandbox](sandboxes-creating#terminating-a-sandbox).

A command finishing does not terminate the sandbox. It remains available for additional commands until you terminate it or its [runtime timeout](sandboxes-creating#lifetime) expires. It can also be [automatically suspended when idle](sandboxes-creating#suspension-on-idle).

Background processes do not keep a sandbox active after the command that started them finishes. If you need these processes to continue without interruption, [disable automatic idle suspension](sandboxes-creating#suspension-on-idle).