# Run commands

Run a shell command inside a sandbox and read its result. You can also stream
its output as it arrives, keep it running in the background, feed it stdin, and
handle a non-zero exit.

Commands run over the sandbox's own connection, not the public REST API, so
there is no REST endpoint and no curl example here. Use the TypeScript or
Python SDK. Every example reads `IMPELLO_API_KEY` from the environment; get a
key at [dashboard.impello.ai/keys](https://dashboard.impello.ai/keys).

## Run a command

`sandbox.commands.run()` starts the command, waits for it to exit and returns
its stdout, stderr and exit code. Every command runs through `/bin/bash -l -c`,
so pipes, globs, `&&` and shell variables work as they do in a terminal.

<CodeTabs>
<Tab label="TypeScript">

```ts
import { Sandbox } from '@impello/sdk'

const sandbox = await Sandbox.create('base')

const result = await sandbox.commands.run('echo hello && uname -a')
console.log(result.stdout)
console.log(result.exitCode) // 0

await sandbox.kill()
```

</Tab>
<Tab label="Python">

```python
from impello import Sandbox

sandbox = Sandbox.create("base")

result = sandbox.commands.run("echo hello && uname -a")
print(result.stdout)
print(result.exit_code)  # 0

sandbox.kill()
```

</Tab>
</CodeTabs>

The result has four fields: `stdout`, `stderr`, `exitCode` (`exit_code` in
Python) and an optional `error` message. Both streams are decoded as UTF-8 and
returned as strings.

### Options

<div className="docs-table-wrap">
<table>
  <thead>
    <tr><th>TypeScript</th><th>Python</th><th>Default</th><th>What it does</th></tr>
  </thead>
  <tbody>
    <tr><td>`cwd`</td><td>`cwd`</td><td>the user's home</td><td>Working directory for the command.</td></tr>
    <tr><td>`user`</td><td>`user`</td><td>the template's user</td><td>Linux user to run as — any user that exists in the template. `base` has `user`, with home `/home/user`, and `root`.</td></tr>
    <tr><td>`envs`</td><td>`envs`</td><td>`{}`</td><td>Environment variables for this command. They override the ones given to the sandbox.</td></tr>
    <tr><td>`onStdout`, `onStderr`</td><td>`on_stdout`, `on_stderr`</td><td>none</td><td>Callbacks that receive output chunks as they arrive. See [Stream output](#stream-output).</td></tr>
    <tr><td>`stdin`</td><td>`stdin`</td><td>`false`</td><td>Keep stdin open so you can write to it. See [Send input](#send-input).</td></tr>
    <tr><td>`background`</td><td>`background`</td><td>`false`</td><td>Return a handle at once instead of waiting. See [Background commands](#background-commands).</td></tr>
    <tr><td>`timeoutMs`</td><td>`timeout`</td><td>`60000` ms / `60` s</td><td>How long the SDK waits for the command. See [Long-running commands](#long-running-commands).</td></tr>
  </tbody>
</table>
</div>

## Stream output

Pass `onStdout` and `onStderr` to receive output while the command is still
running. The callbacks get each chunk as a string; the full output is still
collected in the returned result.

<CodeTabs>
<Tab label="TypeScript">

```ts
import { Sandbox } from '@impello/sdk'

const sandbox = await Sandbox.create('base')

await sandbox.commands.run('for i in 1 2 3; do echo $i; sleep 1; done', {
  onStdout: (data) => {
    process.stdout.write(data)
  },
  onStderr: (data) => {
    process.stderr.write(data)
  },
})

await sandbox.kill()
```

</Tab>
<Tab label="Python">

```python
from impello import Sandbox

sandbox = Sandbox.create("base")

sandbox.commands.run(
    "for i in 1 2 3; do echo $i; sleep 1; done",
    on_stdout=lambda data: print(data, end=""),
    on_stderr=lambda data: print(data, end=""),
)

sandbox.kill()
```

</Tab>
</CodeTabs>

In the Python sync SDK a background handle is also iterable. Each item is a
`(stdout, stderr, pty)` tuple with one field set and the other two `None`:

```python
handle = sandbox.commands.run("ls -la /", background=True)

for stdout, stderr, _ in handle:
    if stdout is not None:
        print(stdout, end="")
```

In the sync SDK the `on_stdout` and `on_stderr` arguments to `run()` apply to
foreground commands only. For a background command, iterate the handle or pass
them to `handle.wait(on_stdout=..., on_stderr=...)`.

With `AsyncSandbox`, pass `on_stdout` and `on_stderr` to `run()` for both
foreground and background commands; `AsyncCommandHandle.wait()` takes no
callbacks.

## Background commands

Set `background: true` and `run()` returns a `CommandHandle` immediately. Use
the handle to wait for the exit, read the output so far, or kill the process.

<CodeTabs>
<Tab label="TypeScript">

```ts
import { Sandbox } from '@impello/sdk'

const sandbox = await Sandbox.create('base')

const handle = await sandbox.commands.run('sleep 2 && echo done', {
  background: true,
})
console.log(handle.pid)
console.log(handle.exitCode) // undefined while running

const result = await handle.wait()
console.log(result.stdout) // "done\n"

await sandbox.kill()
```

</Tab>
<Tab label="Python">

```python
from impello import Sandbox

sandbox = Sandbox.create("base")

handle = sandbox.commands.run("sleep 2 && echo done", background=True)
print(handle.pid)

result = handle.wait()
print(result.stdout)  # "done\n"

sandbox.kill()
```

</Tab>
</CodeTabs>

In TypeScript the handle exposes `pid`, `stdout`, `stderr`, `exitCode` and
`error`; `exitCode` is `undefined` until the command exits. The Python sync
handle exposes `pid` and the methods below; the async handle adds `stdout`,
`stderr`, `exit_code` and `error`, with `exit_code` `None` while running.

`handle.disconnect()` stops the SDK listening without killing the command. To
pick it up again, call `sandbox.commands.connect(pid)` from any client
connected to the same sandbox; the new handle's `wait()` returns the result
when the command exits.

<CodeTabs>
<Tab label="TypeScript">

```ts
const handle = await sandbox.commands.run('sleep 30 && echo done', {
  background: true,
})
await handle.disconnect()

const again = await sandbox.commands.connect(handle.pid, {
  onStdout: (data) => console.log(data),
})
await again.wait()
```

</Tab>
<Tab label="Python">

```python
handle = sandbox.commands.run("sleep 30 && echo done", background=True)
handle.disconnect()

again = sandbox.commands.connect(handle.pid)
again.wait(on_stdout=lambda data: print(data, end=""))
```

</Tab>
</CodeTabs>

## Long-running commands

The SDK stays attached to a command for 60 seconds by default. When the limit
passes it throws `TimeoutError` (`TimeoutException` in Python) from `run()` or
`wait()`. That stops the SDK waiting; find the process with
`sandbox.commands.list()`, then re-attach with `sandbox.commands.connect(pid)`
to see whether it is still running, or stop it with
`sandbox.commands.kill(pid)`. Raise the limit with `timeoutMs` (`timeout` in
Python, in seconds) to cover the longest run you expect. `background: true`
does not change this: the handle is bounded by the same value, so pass the
option there too.

<CodeTabs>
<Tab label="TypeScript">

```ts
const result = await sandbox.commands.run('npm install', {
  timeoutMs: 10 * 60 * 1000,
})
```

</Tab>
<Tab label="Python">

```python
result = sandbox.commands.run("npm install", timeout=10 * 60)
```

</Tab>
</CodeTabs>

`connect()` has the same 60 second default and takes the same option.

<Callout kind="warning">
`timeoutMs: 0` in TypeScript and `timeout=0` in Python both remove the limit;
the SDK then waits as long as the command runs. That wait is still cut short
when the sandbox itself reaches its own timeout.
</Callout>

The command timeout is separate from the sandbox timeout. A sandbox that
reaches its own limit is killed with everything running inside it, unless its
`lifecycle` pauses it instead. A `wait()` still pending on one of its commands
then throws `TimeoutError`. See [Timeouts](/docs/sandbox/timeouts).

<Callout kind="note">
The SDK keeps the whole of stdout and stderr in memory as strings and does not
truncate them. For a command that prints a lot, stream it with `onStdout` or
write it to a file and read that file with the
[Filesystem](/docs/sandbox/filesystem) module.
</Callout>

## Send input

Start the command with `stdin: true`, write to it with `sendStdin()`, and call
`closeStdin()` to send EOF when you are done. Both methods also exist on
`sandbox.commands` taking a `pid`, for a command you did not start from this
handle.

<CodeTabs>
<Tab label="TypeScript">

```ts
import { Sandbox } from '@impello/sdk'

const sandbox = await Sandbox.create('base')

const handle = await sandbox.commands.run('cat', {
  background: true,
  stdin: true,
})
await handle.sendStdin('hello from stdin\n')
await handle.closeStdin()

const result = await handle.wait()
console.log(result.stdout) // "hello from stdin\n"

await sandbox.kill()
```

</Tab>
<Tab label="Python">

```python
from impello import Sandbox

sandbox = Sandbox.create("base")

handle = sandbox.commands.run("cat", background=True, stdin=True)
handle.send_stdin("hello from stdin\n")
handle.close_stdin()

result = handle.wait()
print(result.stdout)  # "hello from stdin\n"

sandbox.kill()
```

</Tab>
</CodeTabs>

The data may be a string or raw bytes — a `Uint8Array` in TypeScript, `bytes`
in Python. A handle for a pseudo-terminal supports neither method; use the
[Interactive terminal](/docs/sandbox/pty) module for that instead.

## List and kill processes

`sandbox.commands.list()` returns every running command and PTY session as a
`ProcessInfo` with `pid`, `cmd`, `args`, `envs`, and optional `cwd` and `tag`.
Because every command runs through bash, `cmd` is `/bin/bash` and your command
line is the last element of `args`.

`sandbox.commands.kill(pid)` sends `SIGKILL`. It returns `true` when the signal
was sent and `false` when no process had that pid. `handle.kill()` does the
same for the handle's own process.

<CodeTabs>
<Tab label="TypeScript">

```ts
import { Sandbox } from '@impello/sdk'

const sandbox = await Sandbox.create('base')

await sandbox.commands.run('sleep 600', { background: true })

for (const proc of await sandbox.commands.list()) {
  console.log(proc.pid, proc.args.at(-1))
  if (proc.args.at(-1) === 'sleep 600') {
    console.log(await sandbox.commands.kill(proc.pid)) // true
  }
}

await sandbox.kill()
```

</Tab>
<Tab label="Python">

```python
from impello import Sandbox

sandbox = Sandbox.create("base")

sandbox.commands.run("sleep 600", background=True)

for proc in sandbox.commands.list():
    print(proc.pid, proc.args[-1])
    if proc.args[-1] == "sleep 600":
        print(sandbox.commands.kill(proc.pid))  # True

sandbox.kill()
```

</Tab>
</CodeTabs>

## Non-zero exit codes

A command that exits with anything other than `0` makes `run()` and `wait()`
throw `CommandExitError` (`CommandExitException` in Python). The error carries
the same `exitCode`, `stdout` and `stderr` fields as a result, so you can read
what went wrong.

<CodeTabs>
<Tab label="TypeScript">

```ts
import { CommandExitError, Sandbox } from '@impello/sdk'

const sandbox = await Sandbox.create('base')

try {
  await sandbox.commands.run('ls /does-not-exist')
} catch (err) {
  if (err instanceof CommandExitError) {
    console.log(err.exitCode) // 2
    console.log(err.stderr)
  } else {
    throw err
  }
}

await sandbox.kill()
```

</Tab>
<Tab label="Python">

```python
from impello import CommandExitException, Sandbox

sandbox = Sandbox.create("base")

try:
    sandbox.commands.run("ls /does-not-exist")
except CommandExitException as e:
    print(e.exit_code)  # 2
    print(e.stderr)

sandbox.kill()
```

</Tab>
</CodeTabs>

In Python, `str(e)` reads `Command exited with code 2 and error:` followed by
stderr. The other classes a command can raise are listed on the
[Errors](/docs/errors) page.

Next: [Interactive terminal](/docs/sandbox/pty).
