---
title: Execution
description: Python SDK - Command execution API reference
---

Run commands inside a running sandbox. See [Commands](/sandboxes/commands) for usage examples.

## Sandbox

#### <span className="msb-recv">sandbox.</span><span className="msb-hn">exec()</span>

```python
async def exec(
    cmd: str,
    args: list[str] | ExecOptions | None = None,
    *,
    cwd: str | None = None,
    user: str | None = None,
    env: Mapping[str, str] | None = None,
    timeout: float | None = None,
    stdin: Stdin | bytes | None = None,
    tty: bool = False,
    rlimits: list[Rlimit] | None = None,
) -> ExecOutput
```

<Accordion title="Example">

```python
out = await sandbox.exec(
    "python3",
    ["script.py"],
    cwd="/app",
    env={"PYTHONPATH": "/app/lib"},
    timeout=30.0,
)
print(out.stdout_text)
print(out.exit_code)  # 0
```

</Accordion>

Run a command inside the sandbox and wait for it to complete, buffering all stdout and stderr into memory. The keyword-only options apply to this call alone and don't change the sandbox's defaults. For long-running processes or large output, use [`exec_stream()`](#sandbox-exec_stream) instead. Raises [`ExecTimeoutError`](/sdk/python/sandbox) if `timeout` elapses and [`ExecFailedError`](/sdk/python/sandbox) if the process can't be spawned.

<p className="msb-label">Parameters</p>

<div className="msb-params">
  <div className="msb-param">
    <div className="msb-param-key"><code>cmd</code><span className="msb-type">str</span></div>
    <div className="msb-param-desc">Command to execute (e.g. <code>"python3"</code>, <code>"/usr/bin/node"</code>).</div>
  </div>
  <div className="msb-param">
    <div className="msb-param-key"><code>args</code><span className="msb-type">list[str] | ExecOptions | None</span></div>
    <div className="msb-param-desc">Command arguments, or a typed options mapping that may include its own <code>args</code> list.</div>
  </div>
  <div className="msb-param">
    <div className="msb-param-key"><code>cwd</code><span className="msb-type">str | None</span></div>
    <div className="msb-param-desc">Working directory for this command.</div>
  </div>
  <div className="msb-param">
    <div className="msb-param-key"><code>user</code><span className="msb-type">str | None</span></div>
    <div className="msb-param-desc">Guest user to run as.</div>
  </div>
  <div className="msb-param">
    <div className="msb-param-key"><code>env</code><span className="msb-type">Mapping[str, str] | None</span></div>
    <div className="msb-param-desc">Environment variables, merged on top of the sandbox defaults.</div>
  </div>
  <div className="msb-param">
    <div className="msb-param-key"><code>timeout</code><span className="msb-type">float | None</span></div>
    <div className="msb-param-desc">Seconds before the process is killed.</div>
  </div>
  <div className="msb-param">
    <div className="msb-param-key"><code>stdin</code><a className="msb-type" href="#stdin">Stdin | bytes | None</a></div>
    <div className="msb-param-desc">Stdin configuration. Raw <code>bytes</code> are sent inline; default is <code>/dev/null</code>.</div>
  </div>
  <div className="msb-param">
    <div className="msb-param-key"><code>tty</code><span className="msb-type">bool</span></div>
    <div className="msb-param-desc">Allocate a pseudo-terminal, merging stdout and stderr.</div>
  </div>
  <div className="msb-param">
    <div className="msb-param-key"><code>rlimits</code><a className="msb-type" href="#rlimit">list[Rlimit] | None</a></div>
    <div className="msb-param-desc">POSIX resource limits applied to the process.</div>
  </div>
</div>

<p className="msb-label">Returns</p>

<div className="msb-params">
  <div className="msb-param">
    <div className="msb-param-key"><a className="msb-type" href="#execoutput">ExecOutput</a></div>
    <div className="msb-param-desc">Collected stdout, stderr, and exit status.</div>
  </div>
</div>

#### <span className="msb-recv">sandbox.</span><span className="msb-hn">shell()</span>

```python
async def shell(
    script: str,
    *,
    cwd: str | None = None,
    user: str | None = None,
    env: Mapping[str, str] | None = None,
    timeout: float | None = None,
    stdin: Stdin | bytes | None = None,
    tty: bool = False,
    rlimits: list[Rlimit] | None = None,
) -> ExecOutput
```

<Accordion title="Example">

```python
out = await sandbox.shell("ls -la /app && echo done")
print(out.stdout_text)
```

</Accordion>

Run a command through the sandbox's configured shell (defaults to `/bin/sh`). Shell syntax like pipes, redirects, and `&&` chains works. Accepts the same keyword-only options as [`exec()`](#sandbox-exec).

<p className="msb-label">Parameters</p>

<div className="msb-params">
  <div className="msb-param">
    <div className="msb-param-key"><code>script</code><span className="msb-type">str</span></div>
    <div className="msb-param-desc">Shell command string (e.g. <code>"ls -la /app &amp;&amp; echo done"</code>).</div>
  </div>
  <div className="msb-param">
    <div className="msb-param-key"><code>cwd, user, env, timeout, stdin, tty, rlimits</code></div>
    <div className="msb-param-desc">Same per-call options as <a className="msb-type" href="#sandbox-exec">exec()</a>.</div>
  </div>
</div>

<p className="msb-label">Returns</p>

<div className="msb-params">
  <div className="msb-param">
    <div className="msb-param-key"><a className="msb-type" href="#execoutput">ExecOutput</a></div>
    <div className="msb-param-desc">Collected stdout, stderr, and exit status.</div>
  </div>
</div>

<p className="msb-member-group">Stream methods</p>

#### <span className="msb-recv">sandbox.</span><span className="msb-hn">exec_stream()</span>

```python
async def exec_stream(
    cmd: str,
    args: list[str] | ExecOptions | None = None,
    *,
    cwd: str | None = None,
    user: str | None = None,
    env: Mapping[str, str] | None = None,
    timeout: float | None = None,
    stdin: Stdin | bytes | None = None,
    tty: bool = False,
    rlimits: list[Rlimit] | None = None,
) -> ExecHandle
```

<Accordion title="Example">

```python
import sys

from microsandbox import ExecEventType

handle = await sandbox.exec_stream("tail", ["-f", "/var/log/app.log"])
async for event in handle:
    if event.event_type is ExecEventType.STDOUT:
        sys.stdout.buffer.write(event.data)
    elif event.event_type is ExecEventType.EXITED:
        break
```

</Accordion>

Run a command with streaming output. Returns an [`ExecHandle`](#exechandle) that emits stdout, stderr, and exit events as they happen rather than buffering everything. Takes the same per-call options as [`exec()`](#sandbox-exec). Pass `stdin=Stdin.pipe()` to write to the process while it runs via [`take_stdin()`](#exechandle). For TTY sessions, call [`resize(rows, cols)`](#exechandle) when the terminal dimensions change.

<p className="msb-label">Parameters</p>

<div className="msb-params">
  <div className="msb-param">
    <div className="msb-param-key"><code>cmd</code><span className="msb-type">str</span></div>
    <div className="msb-param-desc">Command to execute.</div>
  </div>
  <div className="msb-param">
    <div className="msb-param-key"><code>args</code><span className="msb-type">list[str] | ExecOptions | None</span></div>
    <div className="msb-param-desc">Command arguments, or a typed options mapping.</div>
  </div>
  <div className="msb-param">
    <div className="msb-param-key"><code>cwd, user, env, timeout, stdin, tty, rlimits</code></div>
    <div className="msb-param-desc">Same per-call options as <a className="msb-type" href="#sandbox-exec">exec()</a>.</div>
  </div>
</div>

<p className="msb-label">Returns</p>

<div className="msb-params">
  <div className="msb-param">
    <div className="msb-param-key"><a className="msb-type" href="#exechandle">ExecHandle</a></div>
    <div className="msb-param-desc">Streaming handle for receiving events and controlling the process.</div>
  </div>
</div>

#### <span className="msb-recv">sandbox.</span><span className="msb-hn">shell_stream()</span>

```python
async def shell_stream(
    script: str,
    *,
    cwd: str | None = None,
    user: str | None = None,
    env: Mapping[str, str] | None = None,
    timeout: float | None = None,
    stdin: Stdin | bytes | None = None,
    tty: bool = False,
    rlimits: list[Rlimit] | None = None,
) -> ExecHandle
```

<Accordion title="Example">

```python
import sys

from microsandbox import ExecEventType

handle = await sandbox.shell_stream("for i in 1 2 3; do echo $i; sleep 1; done")
async for event in handle:
    if event.event_type is ExecEventType.STDOUT:
        sys.stdout.buffer.write(event.data)
```

</Accordion>

Streaming variant of [`shell()`](#sandbox-shell): runs `script` through the configured shell but returns an [`ExecHandle`](#exechandle) instead of buffering output.

<p className="msb-label">Parameters</p>

<div className="msb-params">
  <div className="msb-param">
    <div className="msb-param-key"><code>script</code><span className="msb-type">str</span></div>
    <div className="msb-param-desc">Shell command string.</div>
  </div>
  <div className="msb-param">
    <div className="msb-param-key"><code>cwd, user, env, timeout, stdin, tty, rlimits</code></div>
    <div className="msb-param-desc">Same per-call options as <a className="msb-type" href="#sandbox-exec">exec()</a>.</div>
  </div>
</div>

<p className="msb-label">Returns</p>

<div className="msb-params">
  <div className="msb-param">
    <div className="msb-param-key"><a className="msb-type" href="#exechandle">ExecHandle</a></div>
    <div className="msb-param-desc">Streaming handle.</div>
  </div>
</div>

<p className="msb-member-group">Attach methods</p>

#### <span className="msb-recv">sandbox.</span><span className="msb-hn">attach()</span>

```python
async def attach(
    cmd: str,
    args: list[str] | None = None,
    *,
    cwd: str | None = None,
    user: str | None = None,
    env: Mapping[str, str] | None = None,
    detach_keys: str | None = None,
) -> int
```

<Accordion title="Example">

```python
code = await sandbox.attach("bash", cwd="/app", env={"EDITOR": "vim"})
```

</Accordion>

Bridge your terminal directly to a process inside the sandbox for a fully interactive PTY session. Press the configured detach key sequence (default `Ctrl+]`) to disconnect without stopping the process. Returns the process exit code.

<p className="msb-label">Parameters</p>

<div className="msb-params">
  <div className="msb-param">
    <div className="msb-param-key"><code>cmd</code><span className="msb-type">str</span></div>
    <div className="msb-param-desc">Command to run.</div>
  </div>
  <div className="msb-param">
    <div className="msb-param-key"><code>args</code><span className="msb-type">list[str] | None</span></div>
    <div className="msb-param-desc">Command arguments.</div>
  </div>
  <div className="msb-param">
    <div className="msb-param-key"><code>cwd</code><span className="msb-type">str | None</span></div>
    <div className="msb-param-desc">Working directory.</div>
  </div>
  <div className="msb-param">
    <div className="msb-param-key"><code>user</code><span className="msb-type">str | None</span></div>
    <div className="msb-param-desc">Guest user to run as.</div>
  </div>
  <div className="msb-param">
    <div className="msb-param-key"><code>env</code><span className="msb-type">Mapping[str, str] | None</span></div>
    <div className="msb-param-desc">Environment variables for the session.</div>
  </div>
  <div className="msb-param">
    <div className="msb-param-key"><code>detach_keys</code><span className="msb-type">str | None</span></div>
    <div className="msb-param-desc">Detach key sequence (e.g. <code>"ctrl-]"</code> or <code>"ctrl-p,ctrl-q"</code>).</div>
  </div>
</div>

<p className="msb-label">Returns</p>

<div className="msb-params">
  <div className="msb-param">
    <div className="msb-param-key"><span className="msb-type">int</span></div>
    <div className="msb-param-desc">Exit code of the process.</div>
  </div>
</div>

#### <span className="msb-recv">sandbox.</span><span className="msb-hn">attach_shell()</span>

```python
async def attach_shell() -> int
```

<Accordion title="Example">

```python
code = await sandbox.attach_shell()
```

</Accordion>

Bridge your terminal to the sandbox's default shell in a fully interactive PTY session. Returns the shell's exit code.

<p className="msb-label">Returns</p>

<div className="msb-params">
  <div className="msb-param">
    <div className="msb-param-key"><span className="msb-type">int</span></div>
    <div className="msb-param-desc">Exit code of the shell process.</div>
  </div>
</div>

## ExecHandle


<p className="msb-backref">Returned by <a href="#sandbox-exec_stream">exec_stream()</a> · <a href="#sandbox-shell_stream">shell_stream()</a></p>

A handle to a running streaming execution and its events.

#### <span className="msb-recv">handle.</span><span className="msb-hn">id</span>

`str`

Correlation ID for this execution

#### <span className="msb-recv">handle.</span><span className="msb-hn">take_stdin()</span>

```python
take_stdin()
```

Take the stdin writer. Returns `None` after the first call, or when stdin wasn't piped

<p className="msb-label">Returns</p>

[`ExecSink`](#execsink) ` \| None`

#### <span className="msb-recv">handle.</span><span className="msb-hn">recv()</span>

```python
recv()
```

*(async)* Receive the next event. Returns `None` when the stream ends

<p className="msb-label">Returns</p>

[`ExecEvent`](#execevent) ` \| None`

#### <span className="msb-recv">handle.</span><span className="msb-hn">wait()</span>

```python
wait()
```

*(async)* Wait for the process to exit. Returns `(code, success)`

<p className="msb-label">Returns</p>

`tuple[int, bool]`

#### <span className="msb-recv">handle.</span><span className="msb-hn">collect()</span>

```python
collect()
```

*(async)* Drain remaining output and wait for exit

<p className="msb-label">Returns</p>

[`ExecOutput`](#execoutput)

#### <span className="msb-recv">handle.</span><span className="msb-hn">signal()</span>

```python
signal(sig)
```

*(async)* Send a POSIX signal (numeric) to the process

#### <span className="msb-recv">handle.</span><span className="msb-hn">kill()</span>

```python
kill()
```

*(async)* Send SIGKILL to the process

#### <span className="msb-recv">handle.</span><span className="msb-hn">resize()</span>

```python
resize(rows, cols)
```

*(async)* Resize the PTY to the given `uint16` dimensions

## ExecSink


<p className="msb-backref">Returned by <a href="#exechandle">ExecHandle.take_stdin()</a></p>

Writer for sending data to a running process's stdin. Obtained from [`ExecHandle.take_stdin()`](#exechandle) when the execution was configured with `stdin=Stdin.pipe()`.


#### <span className="msb-recv">sink.</span><span className="msb-hn">write()</span>

```python
write(data)
```

*(async)* Write bytes to the process's stdin

#### <span className="msb-recv">sink.</span><span className="msb-hn">close()</span>

```python
close()
```

*(async)* Close the sink. Sends EOF in non-TTY pipe mode; the guest PTY remains open in TTY mode

## Stdin

<p className="msb-backref">Used by <a href="#sandbox-exec">exec()</a> · <a href="#sandbox-shell">shell()</a> · <a href="#sandbox-exec_stream">exec_stream()</a> · <a href="#sandbox-shell_stream">shell_stream()</a></p>

Factory for process stdin configuration.


#### <span className="msb-recv">Stdin.</span><span className="msb-hn">null()</span>

```python
Stdin.null()
```

Connect stdin to `/dev/null` (default)

#### <span className="msb-recv">Stdin.</span><span className="msb-hn">pipe()</span>

```python
Stdin.pipe()
```

Open a writable pipe. Write via [`take_stdin()`](#exechandle) on the handle

#### <span className="msb-recv">Stdin.</span><span className="msb-hn">bytes()</span>

```python
Stdin.bytes(data)
```

Inline data sent before the process starts, then EOF

The factories set the corresponding [`StdinMode`](#stdinmode) member.

## Rlimit

<p className="msb-backref">Used by <a href="#sandbox-exec">exec()</a> · <a href="#sandbox-shell">shell()</a> · <a href="#sandbox-exec_stream">exec_stream()</a> · <a href="#sandbox-shell_stream">shell_stream()</a></p>

Frozen dataclass describing a POSIX resource limit. Construct one directly or via a factory, then pass a list as the `rlimits` argument.

#### <span className="msb-recv">rlimit.</span><span className="msb-hn">resource</span>

[`RlimitResource`](#rlimitresource)

Which resource is limited

#### <span className="msb-recv">rlimit.</span><span className="msb-hn">soft</span>

`int`

Soft limit

#### <span className="msb-recv">rlimit.</span><span className="msb-hn">hard</span>

`int`

Hard limit

#### <span className="msb-recv">Rlimit.</span><span className="msb-hn">nofile()</span>

```python
Rlimit.nofile(limit)
```

Max open file descriptors

#### <span className="msb-recv">Rlimit.</span><span className="msb-hn">cpu()</span>

```python
Rlimit.cpu(secs)
```

CPU time limit in seconds

#### <span className="msb-recv">Rlimit.</span><span className="msb-hn">as_()</span>

```python
Rlimit.as_(\*, soft, hard)
```

Virtual memory size

#### <span className="msb-recv">Rlimit.</span><span className="msb-hn">nproc()</span>

```python
Rlimit.nproc(limit)
```

Max number of processes

#### <span className="msb-recv">Rlimit.</span><span className="msb-hn">fsize()</span>

```python
Rlimit.fsize(limit)
```

Max file size

#### <span className="msb-recv">Rlimit.</span><span className="msb-hn">memlock()</span>

```python
Rlimit.memlock(limit)
```

Max locked memory

#### <span className="msb-recv">Rlimit.</span><span className="msb-hn">stack()</span>

```python
Rlimit.stack(limit)
```

Max stack size

## Types

### ExecOptions

<p className="msb-backref">Accepted by <a href="#sandbox-exec">exec()</a> · <a href="#sandbox-exec_stream">exec_stream()</a></p>

Typed dictionary for passing arguments and per-call options together as the second positional argument. Use a [`Stdin`](#stdin) object to select a mode or raw `bytes` for inline data.

| Key | Type | Description |
|-----|------|-------------|
| args | `list[str]` | Command arguments |
| cwd | `str` | Working directory |
| user | `str` | Guest user |
| env | `Mapping[str, str]` | Per-command environment variables |
| timeout | `float` | Timeout in seconds |
| stdin | [`Stdin`](#stdin) `\| bytes` | Stdin configuration or inline bytes |
| stdin_data | `bytes` | Inline stdin bytes when `stdin` is omitted |
| tty | `bool` | Whether to allocate a pseudo-terminal |
| rlimits | `list[`[`Rlimit`](#rlimit)`]` | POSIX resource limits |

### ExecOutput

<p className="msb-backref">Returned by <a href="#sandbox-exec">exec()</a> · <a href="#sandbox-shell">shell()</a> · <a href="#exechandle">ExecHandle.collect()</a></p>

The result of a completed command execution: collected output plus exit status. All members are properties. In TTY mode, `stdout_bytes` contains the combined terminal output and `stderr_bytes` is empty because a PTY doesn't preserve separate stdout and stderr streams.

| Property | Type | Description |
|----------|------|-------------|
| exit_code | `int` | Process exit code |
| success | `bool` | `True` when `exit_code == 0` |
| stdout_text | `str` | Collected stdout decoded as UTF-8. Raises on invalid encoding |
| stderr_text | `str` | Collected stderr decoded as UTF-8. Raises on invalid encoding |
| stdout_bytes | `bytes` | Raw stdout bytes |
| stderr_bytes | `bytes` | Raw stderr bytes |

### ExecEvent

<p className="msb-backref">Emitted by <a href="#exechandle">ExecHandle</a></p>

Native event object emitted by [`recv()`](#exechandle) and by iterating an [`ExecHandle`](#exechandle). Fields that don't apply to a given event are `None`.

| Property | Type | Description |
|----------|------|-------------|
| event_type | [`ExecEventType`](#execeventtype) | Exact event discriminator |
| pid | `int \| None` | Guest PID, set on `ExecEventType.STARTED` |
| data | `bytes \| None` | Output bytes on `STDOUT` / `STDERR`, or a UTF-8 failure message on `FAILED` / `STDIN_ERROR` |
| code | `int \| None` | Exit code on `EXITED`, or errno when available on `FAILED` / `STDIN_ERROR` |

### ExecEventType

<p className="msb-backref">Returned by <a href="#execevent">ExecEvent.event_type</a></p>

String enum (`enum.StrEnum`) identifying a streaming execution event.

| Member | Value | Description |
|--------|-------|-------------|
| `ExecEventType.STARTED` | `"started"` | Process started; `pid` is populated |
| `ExecEventType.STDOUT` | `"stdout"` | Standard-output bytes are available in `data` |
| `ExecEventType.STDERR` | `"stderr"` | Standard-error bytes are available in `data` |
| `ExecEventType.EXITED` | `"exited"` | Process exited; `code` is populated |
| `ExecEventType.FAILED` | `"failed"` | Process startup failed |
| `ExecEventType.STDIN_ERROR` | `"stdin_error"` | Writing process stdin failed |

### ExitStatus

<p className="msb-backref">Used by <a href="#exechandle">ExecHandle.wait()</a></p>

Frozen dataclass describing a process exit result. [`ExecHandle.wait()`](#exechandle) returns the same information as a `(code, success)` tuple.

| Field | Type | Description |
|-------|------|-------------|
| code | `int` | Process exit code |
| success | `bool` | `True` when `code == 0` |

### StdinMode

<p className="msb-backref">Used internally by <a href="#stdin">Stdin</a></p>

String enum (`enum.StrEnum`) identifying how command stdin is connected.

| Member | Value | Description |
|--------|-------|-------------|
| `StdinMode.NULL` | `"null"` | Connect stdin to `/dev/null` |
| `StdinMode.PIPE` | `"pipe"` | Expose a writable [`ExecSink`](#execsink) |
| `StdinMode.BYTES` | `"bytes"` | Send inline bytes, then EOF |

### RlimitResource

<p className="msb-backref">Used by <a href="#rlimit">Rlimit.resource</a></p>

String enum (`enum.StrEnum`) naming a limitable POSIX resource.

| Member | Value | Description |
|--------|-------|-------------|
| `RlimitResource.CPU` | `"cpu"` | CPU time |
| `RlimitResource.FSIZE` | `"fsize"` | File size |
| `RlimitResource.DATA` | `"data"` | Data segment size |
| `RlimitResource.STACK` | `"stack"` | Stack size |
| `RlimitResource.CORE` | `"core"` | Core file size |
| `RlimitResource.RSS` | `"rss"` | Resident set size |
| `RlimitResource.NPROC` | `"nproc"` | Number of processes |
| `RlimitResource.NOFILE` | `"nofile"` | Open file descriptors |
| `RlimitResource.MEMLOCK` | `"memlock"` | Locked memory |
| `RlimitResource.AS` | `"as"` | Virtual memory |
| `RlimitResource.LOCKS` | `"locks"` | File locks |
| `RlimitResource.SIGPENDING` | `"sigpending"` | Pending signals |
| `RlimitResource.MSGQUEUE` | `"msgqueue"` | Message queue size |
| `RlimitResource.NICE` | `"nice"` | Nice priority ceiling |
| `RlimitResource.RTPRIO` | `"rtprio"` | Real-time priority ceiling |
| `RlimitResource.RTTIME` | `"rttime"` | Real-time CPU time |
