# Commands (/docs/sdk/commands)



`client.commands` runs processes inside a sandbox and tracks each one as a
Command record with status, output, and logs. `run` waits for the process to
exit by default. Set `background: true` to return immediately while it runs.

<Callout type="info" title="Note">
  A non-zero exit code is not an error. `run` resolves normally; inspect
  `command.exitCode` and `command.status`. See [Errors](/docs/sdk/errors).
</Callout>

## run [#run]

Execute a command and wait for it to exit. The resolved `Command` includes
`exitCode`, `stdout`, and `stderr`.

```ts
run(
  sandboxId: `sbx_${string}`,
  command: string,
  input?: RunCommandOptions,
): Promise<Command>
```

`RunCommandOptions`:

| Field            | Type                                | Default       | Description                                                                                                                                        |
| ---------------- | ----------------------------------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `cwd`            | `string`                            | `/workspace`  | Working directory for the process.                                                                                                                 |
| `env`            | `Record<string, string>`            | —             | Extra environment variables. Keys with the reserved `CROWNEST` prefix are rejected (`reserved_env_key`). Values are ephemeral and never persisted. |
| `timeoutMs`      | `number`                            | `60000`       | Maximum runtime in milliseconds (maximum 600000).                                                                                                  |
| `background`     | `boolean`                           | `false`       | Return immediately while the command continues. Background commands can't use `collect` or `collectOn`.                                            |
| `collect`        | `{ path: string; name?: string }[]` | —             | Files to export as artifacts after the command finishes. Requires the `artifact:create` scope.                                                     |
| `collectOn`      | `"success" \| "always"`             | `"success"`   | When to collect: only on exit code 0, or always.                                                                                                   |
| `idempotencyKey` | `string`                            | SDK-generated | Idempotency key with a 24-hour retry window.                                                                                                       |
| `onStdout`       | `(chunk: string) => void`           | —             | Called with stdout chunks as they stream while the command runs.                                                                                   |
| `onStderr`       | `(chunk: string) => void`           | —             | Called with stderr chunks as they stream while the command runs.                                                                                   |
| `onStreamError`  | `(error: unknown) => void`          | —             | Called if the underlying log stream fails. Callbacks work for foreground and background commands.                                                  |

```ts
const result = await client.commands.run(sandbox.id, "pytest -q", {
  cwd: "/workspace/app",
  timeoutMs: 120_000,
  collect: [{ path: "report.xml", name: "test-report" }],
  collectOn: "always",
});

console.log(result.status, result.exitCode);
console.log(result.stdout);
```

When `collect` is set, the command's `collectStatus` reports the outcome
(`succeeded`, `partial`, `failed`, `skipped`, `pending`, or
`not_requested`), with per-file details in `collectErrors`.

Error codes: `invalid_request`, `forbidden`, `not_found`,
`sandbox_destroyed`, `quota_exceeded`.

Run a command in the background when you need the Command record immediately.
The TypeScript input union rejects `collect` and `collectOn` when
`background: true`.

```ts
const cmd = await client.commands.run(sandbox.id, "python3 server.py", {
  background: true,
});
```

## get [#get]

Fetch the current state of a command.

```ts
get(commandId: `cmd_${string}`): Promise<Command>
```

```ts
const cmd = await client.commands.get("cmd_abc123");
console.log(cmd.status, cmd.exitCode);
```

## cancel [#cancel]

Cancel a running command. Graceful mode sends `SIGTERM`; force mode sends
`SIGKILL`. The operation is idempotent by state.

```ts
cancel(
  commandId: `cmd_${string}`,
  input?: { mode?: "graceful" | "force" },
): Promise<Command>
```

| Field  | Type                    | Default      | Description                   |
| ------ | ----------------------- | ------------ | ----------------------------- |
| `mode` | `"graceful" \| "force"` | `"graceful"` | How to terminate the process. |

```ts
const canceled = await client.commands.cancel(cmd.id, { mode: "force" });
console.log(canceled.status); // "canceled"
```

## logs [#logs]

Fetch stored log chunks for a command.

```ts
logs(
  commandId: `cmd_${string}`,
  input?: { afterSeq?: number; limit?: number },
): Promise<ListCommandLogsResponse>
```

| Field      | Type     | Default | Description                               |
| ---------- | -------- | ------- | ----------------------------------------- |
| `afterSeq` | `number` | —       | Return chunks after this sequence number. |
| `limit`    | `number` | `100`   | Maximum chunks to return (maximum 100).   |

Each `CommandLogChunk` is
`{ commandId, createdAt, data, seq, stream: "stdout" | "stderr" }`.

```ts
const chunks = await client.commands.logs(cmd.id, { limit: 100 });
for (const chunk of chunks.data) {
  process.stdout.write(chunk.data);
}
```

## streamLogs [#streamlogs]

Stream logs live over server-sent events as an async iterable. The stream
ends with a `terminal` event carrying the final command state.

```ts
streamLogs(
  commandId: `cmd_${string}`,
  input?: CommandStreamInput,
): AsyncIterable<CommandLogStreamEvent>
```

`timeoutMs` bounds connection establishment and becomes the default stream
inactivity window after response headers arrive. Set `streamIdleTimeoutMs` to
override only the inactivity phase. Every received byte, including a server
heartbeat, resets that deadline. You can also pass `signal` to cancel the
stream directly.

`CommandLogStreamEvent` is a union:

| Event       | Shape                                                                 |
| ----------- | --------------------------------------------------------------------- |
| `log`       | `{ type: "log", stream: "stdout" \| "stderr", data, seq, createdAt }` |
| `heartbeat` | `{ type: "heartbeat" }`                                               |
| `terminal`  | `{ type: "terminal", command }` — final `Command` state               |
| `error`     | `{ type: "error", code, message }`                                    |

Run a background command, then read events until the terminal event:

```ts
const cmd = await client.commands.run(sandbox.id, "npm test", {
  background: true,
});

for await (const event of client.commands.streamLogs(cmd.id)) {
  if (event.type === "log") process.stdout.write(event.data);
  if (event.type === "error") throw new Error(event.message);
  if (event.type === "terminal") {
    console.log("exit code:", event.command.exitCode);
    break;
  }
}
```

Pass `afterSeq` to resume after the last sequence number you've seen. If
the requested sequence is outside the retention window, the stream emits
an `error` event with code `stream_gap`.

## Command object [#command-object]

The `Command` type includes `id` (`cmd_` prefix), `sandboxId`, `command`,
`cwd`, `env`, `status` (`queued`, `starting`, `running`, `exited`,
`failed`, `canceled`, `timed_out`, `killed`), `exitCode`, `stdout`,
`stderr`, `stdoutTruncated`, `stderrTruncated`, `startedAt`, `finishedAt`,
`durationMs`, `collectStatus`, `collectErrors`, `cancelMode`,
`canceledAt`, `killedReason` (`sandbox_destroyed`), and
`terminationSignal` (`SIGTERM` or `SIGKILL`).

## Next steps [#next-steps]

* [Files](/docs/sdk/files) — write inputs and read outputs.
* [Artifacts](/docs/sdk/artifacts) — keep command outputs after the
  sandbox is gone.
* [Errors](/docs/sdk/errors) — error codes and retry guidance.
