Commands
Run, start, cancel, and stream logs for commands with client.commands in the TypeScript SDK.
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; start returns immediately so you can stream logs while it runs.
[!NOTE] A non-zero exit code is not an error.
runresolves normally; inspectcommand.exitCodeandcommand.status. See Errors.
run
Execute a command and wait for it to exit. The resolved Command includes
exitCode, stdout, and stderr.
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). |
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. |
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, command_timed_out.
start
Start a command without waiting for it to exit. The returned Command has
status queued or starting; follow it with get or streamLogs.
start doesn't accept collect or collectOn.
start(
sandboxId: `sbx_${string}`,
command: string,
input?: Omit<RunCommandOptions, "collect" | "collectOn">,
): Promise<Command>const cmd = await client.commands.start(sandbox.id, "python server.py");get
Fetch the current state of a command.
get(commandId: `cmd_${string}`): Promise<Command>const cmd = await client.commands.get("cmd_abc123");
console.log(cmd.status, cmd.exitCode);cancel
Cancel a running command. Graceful mode sends SIGTERM; force mode sends
SIGKILL. The operation is idempotent by state.
cancel(
commandId: `cmd_${string}`,
input?: { mode?: "graceful" | "force" },
): Promise<Command>| Field | Type | Default | Description |
|---|---|---|---|
mode | "graceful" | "force" | "graceful" | How to terminate the process. |
const canceled = await client.commands.cancel(cmd.id, { mode: "force" });
console.log(canceled.status); // "canceled"logs
Fetch stored log chunks for a command.
logs(
commandId: `cmd_${string}`,
input?: { afterSeq?: number; limit?: number },
): Promise<readonly CommandLogChunk[]>| Field | Type | Default | Description |
|---|---|---|---|
afterSeq | number | — | Return chunks after this sequence number. |
limit | number | 50 | Maximum chunks to return (maximum 100). |
Each CommandLogChunk is
{ commandId, createdAt, data, seq, stream: "stdout" | "stderr" }.
const chunks = await client.commands.logs(cmd.id, { limit: 100 });
for (const chunk of chunks) {
process.stdout.write(chunk.data);
}streamLogs
Stream logs live over server-sent events as an async iterable. The stream
ends with a terminal event carrying the final command state.
streamLogs(
commandId: `cmd_${string}`,
input?: { afterSeq?: number },
): AsyncIterable<CommandLogStreamEvent>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 } |
A typical streaming loop starts a command, prints log data as it arrives, and reads the final state from the terminal event:
const cmd = await client.commands.start(sandbox.id, "npm test");
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
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).