Commands
Run, cancel, wait for, 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 by default. Set background: true to return immediately while it runs.
Note
A non-zero exit code is not an error. run resolves normally; inspect
command.exitCode and command.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). |
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. |
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.
const cmd = await client.commands.run(sandbox.id, "python3 server.py", {
background: true,
});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<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" }.
const chunks = await client.commands.logs(cmd.id, { limit: 100 });
for (const chunk of chunks.data) {
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?: 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:
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
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).