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

# Run a Background Process

> Start a long command or a server, read its output by offset, reconnect from another process, and cancel it.

A background process is a command that keeps running in the sandbox after your client disconnects,
crashes, or restarts. Any program that holds its ID can read its output later.

**Before You Begin:** `BOXROUTER_API_KEY` set and a client installed, as in the [quickstart](https://boxrouter.ai/docs/quickstart.md). The CLI examples that send input use `jq`.

## Start It and Wait for the Exit

`start` runs your Bash command in a login shell and returns a process object right away. `wait` reads until the
process has exited, then returns the same result as `exec`: `stdout` and `stderr` as text, and `exit_code`.
Ask for `output="bytes"` (`output: 'bytes'` in TypeScript) to get the exact bytes of binary output.

```python Python theme={"theme":"css-variables"}
from boxrouter import BoxRouter

with BoxRouter() as client:
    with client.sandboxes.create() as box:
        process = box.processes.start("for i in 1 2 3; do echo step-$i; sleep 1; done; exit 7")
        result = process.wait()
        print(result.stdout, end="")
        print("exit code:", result.exit_code)
```

```typescript TypeScript theme={"theme":"css-variables"}
import { BoxRouter } from '@boxrouter/sdk';

await using client = new BoxRouter();
await using box = await client.sandboxes.create();
const running = await box.processes.start('for i in 1 2 3; do echo step-$i; sleep 1; done; exit 7');
const result = await running.wait();
process.stdout.write(result.stdout);
console.log('exit code:', result.exit_code);
```

```bash CLI theme={"theme":"css-variables"}
(
  set -euo pipefail
  id=$(boxrouter create)
  trap 'boxrouter destroy "$id" >/dev/null' EXIT
  process=$(boxrouter process start "$id" 'for i in 1 2 3; do echo step-$i; sleep 1; done; exit 7')
  boxrouter process wait "$id" "$process" || echo "exit code: $?"
)
```

Output

```text theme={"theme":"css-variables"}
step-1
step-2
step-3
exit code: 7
```

The SDK examples dispose of the sandbox when their scope exits. The CLI example requests deletion on exit. If creation ends without a sandbox ID, follow
[Recover from Errors](https://boxrouter.ai/docs/errors.md) before retrying.

A nonzero exit raises no error: `wait` returns it as `exit_code`, and `boxrouter process wait` exits with it. The
process has no timeout of its own and ends when the sandbox's [lifetime](https://boxrouter.ai/docs/lifecycle.md#lifetime) ends or when you cancel it.

## Advanced: Read Process Output over HTTP

In the CLI, `boxrouter process read SANDBOX_ID PROCESS_ID` prints a running process's new stdout and stderr without waiting, then the `--stdout` and `--stderr` offsets for the next read, or the exit status once the process has ended. Start from offset 0 by passing neither.

A raw HTTP read never waits for new output. It returns a bounded chunk plus `next_stdout` and
`next_stderr`; pass those offsets on the next request so output is neither missed nor repeated.
The response encodes stdout and stderr as Base64. REST has no blocking wait helper, so use the SDK
`wait()` or CLI `process wait` for a complete result. See the [read-process reference](https://boxrouter.ai/docs/reference/read-process.md)
for the request and response fields.

`exit_code` is `null` until the process has ended and both streams have been read to their ends. SDK `wait()`
polls for you. If you give it a timeout, it raises `TimeoutError` (in Python and TypeScript) without stopping
the process.

## Reconnect from Another Process

Save the sandbox ID and the process ID. The first script below starts the process and exits
without destroying anything. On an API that returns output fields, the second script reads the
whole output from offset 0 and destroys the sandbox.

If the start script fails before it prints both IDs, destroy the sandbox yourself; it keeps running and billing.

```python Python theme={"theme":"css-variables"}
# start.py
from boxrouter import BoxRouter

with BoxRouter() as client:
    box = client.sandboxes.create()
    try:
        process = box.processes.start("for i in 1 2 3; do echo step-$i; sleep 2; done; exit 7")
        print(box.id, process.id)
    except Exception:
        box.destroy()
        raise
```

```typescript TypeScript theme={"theme":"css-variables"}
// start.ts
import { BoxRouter } from '@boxrouter/sdk';

await using client = new BoxRouter();
const box = await client.sandboxes.create();
try {
  const running = await box.processes.start('for i in 1 2 3; do echo step-$i; sleep 2; done; exit 7');
  console.log(box.id, running.id);
} catch (error) {
  await box.destroy();
  throw error;
}
```

```bash CLI theme={"theme":"css-variables"}
# start.sh
set -euo pipefail
id=$(boxrouter create)
if ! process=$(boxrouter process start "$id" 'for i in 1 2 3; do echo step-$i; sleep 2; done; exit 7'); then
  boxrouter destroy "$id" >/dev/null
  exit 1
fi
echo "$id $process"
```

Later, from any process, pass both IDs to the second script:

```python Python theme={"theme":"css-variables"}
# resume.py <SANDBOX_ID> <PROCESS_ID>
import sys
from boxrouter import BoxRouter

sandbox_id, process_id = sys.argv[1:]
with BoxRouter() as client:
    box = client.sandboxes.get(sandbox_id)
    process = box.processes.from_id(process_id)
    result = process.wait(timeout=60)
    print(result.stdout, end="")
    print("exit code:", result.exit_code)
    box.destroy()
```

```typescript TypeScript theme={"theme":"css-variables"}
// resume.ts <SANDBOX_ID> <PROCESS_ID>
import { BoxRouter } from '@boxrouter/sdk';

const [sandboxId, processId] = process.argv.slice(2);
await using client = new BoxRouter();
await using box = await client.sandboxes.get(sandboxId);
const result = await box.processes.fromId(processId).wait({ timeoutSeconds: 60 });
process.stdout.write(result.stdout);
console.log('exit code:', result.exit_code);
```

```bash CLI theme={"theme":"css-variables"}
# resume.sh <SANDBOX_ID> <PROCESS_ID>
set -euo pipefail
id=$1 process=$2
trap 'boxrouter destroy "$id" >/dev/null' EXIT
boxrouter process wait "$id" "$process" --timeout 60 || echo "exit code: $?"
```

Output

```text theme={"theme":"css-variables"}
step-1
step-2
step-3
exit code: 7
```

Process output uses sandbox storage, so large output can fill the available space. After the process
finishes, remove output you no longer need. The output is unavailable once the
sandbox ends or is replaced. Read the result promptly: a process result is kept with a limit and can be lost.

If a read returns `410 expired`, the retained output is no longer available. BoxRouter does not
run the command again.

## Cancel and Clean Up

`cancel` (`boxrouter process kill`) ends the process group and returns once BoxRouter has accepted the request.
Call `wait` or `read` to see the exit. Cancelling a finished process succeeds and does nothing.

```python Python theme={"theme":"css-variables"}
from boxrouter import BoxRouter

with BoxRouter() as client:
    with client.sandboxes.create() as box:
        process = box.processes.start("sleep 300")
        process.cancel()
        print("exit code:", process.wait(timeout=30).exit_code)
        process.remove()
```

```typescript TypeScript theme={"theme":"css-variables"}
import { BoxRouter } from '@boxrouter/sdk';

await using client = new BoxRouter();
await using box = await client.sandboxes.create();
const running = await box.processes.start('sleep 300');
await running.cancel();
console.log('exit code:', (await running.wait({ timeoutSeconds: 30 })).exit_code);
await running.remove();
```

```bash CLI theme={"theme":"css-variables"}
(
  set -euo pipefail
  id=$(boxrouter create)
  trap 'boxrouter destroy "$id" >/dev/null' EXIT
  process=$(boxrouter process start "$id" "sleep 300")
  boxrouter process kill "$id" "$process"
  boxrouter process wait "$id" "$process" --timeout 30 || echo "exit code: $?"
  boxrouter api processes remove "$id" "$process" >/dev/null
)
```

Output

```text theme={"theme":"css-variables"}
exit code: 137
```

The status of a cancelled process comes from the signal that ended it, so check for nonzero
instead of an exact value. `remove` deletes a finished process's retained output; afterwards a
read answers `410 expired`.

## Send Input

To send interactive input, use a route that accepts process input after the API reconnects:

1. Name `processes.input` in `requirements` when you check routes with `client.sandboxes.match`, and send the same requirement when creating the sandbox.
2. Check `matched.eligible`. An empty list means no route satisfies the request.

Without the requirement, `start` fails with `not_named` and nothing runs.

```python Python theme={"theme":"css-variables"}
from boxrouter import BoxRouter

with BoxRouter() as client:
    requirements = {"processes": {"input": True}}
    matched = client.sandboxes.match(requirements=requirements)
    if not matched.eligible:
        raise RuntimeError("No route supports reconnectable process input")
    with client.sandboxes.create(requirements=requirements) as box:
        process = box.processes.start("cat", stdin=True)
        process.write_stdin("hi\n")
        process.close_stdin()
        print(process.wait(timeout=20).stdout, end="")
```

```typescript TypeScript theme={"theme":"css-variables"}
import { BoxRouter } from '@boxrouter/sdk';

await using client = new BoxRouter();
const requirements = { processes: { input: true } };
const matched = await client.sandboxes.match({ requirements });
if (matched.eligible.length === 0) throw new Error('No route supports reconnectable process input');
await using box = await client.sandboxes.create({ requirements });
const running = await box.processes.start('cat', { stdin: true });
await running.writeStdin('hi\n');
await running.closeStdin();
const result = await running.wait({ timeoutSeconds: 20 });
process.stdout.write(result.stdout);
```

```bash CLI theme={"theme":"css-variables"}
(
  set -euo pipefail
  requirements='{"processes": {"input": true}}'
  boxrouter match --requirements "$requirements" | jq -e '.eligible | length > 0' >/dev/null
  id=$(boxrouter create --requirements "$requirements")
  trap 'boxrouter destroy "$id" >/dev/null' EXIT
  process=$(boxrouter process start "$id" cat --stdin)
  boxrouter api processes write-stdin "$id" "$process" --body '{"data": "hi\n"}' >/dev/null
  boxrouter api processes close-stdin "$id" "$process" >/dev/null
  boxrouter process wait "$id" "$process" --timeout 20
)
```

Output

```text theme={"theme":"css-variables"}
hi
```

`write_stdin` sends exactly your text, with no newline added. [Write process input](https://boxrouter.ai/docs/reference/process-stdin.md)
states what input guarantees, including after a reconnect. [Start a process](https://boxrouter.ai/docs/reference/process-start.md)
and [Get observed process state](https://boxrouter.ai/docs/reference/read-process.md) have the full requests.

## Next Steps

> [card] [Work with Files](https://boxrouter.ai/docs/files.md)
>
> Move files in and out of a sandbox.

> [card] [Connect to a Sandbox](https://boxrouter.ai/docs/connect.md)
>
> Open a terminal or a preview URL.

> [card] [How a Sandbox Runs and Ends](https://boxrouter.ai/docs/lifecycle.md)
>
> The clocks that bound a process.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.