# Commands (/docs/concepts/commands)



A command is a top-level process invocation record. You run commands inside
a sandbox either synchronously (`run`, which waits for exit) or
asynchronously (`start`, which returns immediately), then inspect the
result, cancel it, or follow its logs.

## Run vs start [#run-vs-start]

You run a command synchronously or asynchronously, depending on how long the
process runs.

* `POST /v1/sandboxes/{id}/commands/run` waits for the process to exit and
  returns the completed command, including `exitCode`, `stdout`, and
  `stderr`. It also accepts `collect` and `collectOn` to export artifacts
  when the command finishes.
* `POST /v1/sandboxes/{id}/commands/start` returns `202` immediately with
  the command in status `queued` or `starting`. Poll
  `GET /v1/commands/{commandId}` or follow the log stream to track it.
  `start` doesn't accept `collect` or `collectOn`.

Both endpoints take `command` (required, non-empty), plus optional `cwd`,
`env`, and `timeoutMs`. Both are idempotent with an `Idempotency-Key`
header. We recommend `run` for short tasks and `start` for servers and
long-running work.

## Statuses [#statuses]

The `status` field tracks the command through its lifecycle.

| Status      | Meaning                                                                                 |
| ----------- | --------------------------------------------------------------------------------------- |
| `queued`    | Accepted, waiting to start.                                                             |
| `starting`  | The process is launching.                                                               |
| `running`   | The process is executing.                                                               |
| `exited`    | The process exited on its own; check `exitCode`.                                        |
| `failed`    | The command could not run or failed abnormally.                                         |
| `canceled`  | You canceled the command.                                                               |
| `timed_out` | The command exceeded `timeoutMs`.                                                       |
| `killed`    | The sandbox was destroyed while the command ran; `killedReason` is `sandbox_destroyed`. |

## Exit codes vs API errors [#exit-codes-vs-api-errors]

A non-zero exit code is not an API error. If your process runs and exits
with status 3, the API call still succeeds: the command has status `exited`
and `exitCode: 3`. Check `status` and `exitCode` on the command object to
decide whether the work succeeded.

API errors (the `{ "error": { "code", ... } }` envelope) mean the request
itself failed — for example `invalid_request`, `forbidden`, `not_found`,
`sandbox_destroyed`, or `quota_exceeded`.

## Timeouts [#timeouts]

For blocking `run`, `timeoutMs` defaults to 60000 ms (1 minute); `start`
has no default timeout. Both accept up to 600000 ms (10 minutes). A command
that exceeds its timeout ends with status `timed_out`.

## Environment variables [#environment-variables]

Pass `env` as a string map to set environment variables for the process.
Two rules apply:

* You can't set keys with the reserved `CROWNEST` prefix; doing so returns
  `reserved_env_key`. See
  [injected environment context](/docs/concepts/sandboxes#injected-environment-context).
* Values are ephemeral and never persisted. Pass them on each command.

## Cancellation [#cancellation]

Cancel a running command with `POST /v1/commands/{commandId}/cancel`. The
body takes an optional `mode`:

* `graceful` (default) — asks the process to terminate (`SIGTERM`).
* `force` — kills the process immediately (`SIGKILL`).

The canceled command records `cancelMode`, `canceledAt`, and
`terminationSignal`. Cancellation is idempotent by state: canceling an
already-finished command doesn't error.

## Logs [#logs]

Command output is captured as ordered chunks, each with a sequence number
(`seq`) and a stream (`stdout` or `stderr`). There are three ways to get
logs.

### Paginated chunks [#paginated-chunks]

`GET /v1/commands/{commandId}/logs` returns
`{ data, hasMore, nextSeq? }`. Query parameters: `afterSeq` and `limit`
(default 100, max 100). Page with `afterSeq=nextSeq` until `hasMore` is
false.

### Live SSE stream [#live-sse-stream]

`GET /v1/commands/{commandId}/stream` is a server-sent events stream. Event
types are `log`, `heartbeat`, `terminal`, and `error`. Every event has a
`type` and `createdAt`. A `log` event adds `seq`, `data`, and `stream`; a
`terminal` event adds the final `command`; an `error` event adds `code` and
`message`. Resume after a disconnect with
the `afterSeq` query parameter or the `Last-Event-ID` header. If you resume
from a sequence outside the retention window, the stream returns a
`stream_gap` error.

### Full download [#full-download]

`POST /v1/commands/{commandId}/logs/download-url` isn't available yet. It
returns `feature_not_enabled`, since log download URLs require log archive
storage. To get the full output, page the logs endpoint with
`afterSeq`/`nextSeq`.

## Output truncation [#output-truncation]

The `stdout` and `stderr` fields inlined on the command object can be
truncated for large outputs; `stdoutTruncated` and `stderrTruncated` tell
you when that happened. To get the full output, page through the logs
endpoint with `afterSeq`/`nextSeq`.

## Next steps [#next-steps]

* [Sandboxes](/docs/concepts/sandboxes) — lifecycle and TTL behavior that
  affects running commands.
* [Artifacts](/docs/concepts/artifacts) — export files with `collect` and
  `collectOn`.
* [SDK quickstart](/docs/quickstart/sdk) — run your first command.
* [Commands API reference](/docs/api/commands)
