---
title: "Sandbox SDK commands and output"
canonical: https://trickest.com/docs/developer-tools/sandbox-sdk/commands
description: "Run sandbox commands, follow detached logs, and handle timeouts and output limits."
---

# Sandbox SDK commands and output

Use **`@trickest/sandbox`** to run commands and collect their results. These functions take a running `Sandbox` handle from the [quickstart](/docs/developer-tools/sandbox-sdk).

## Run a command and check its result

Pass the executable and its arguments separately. For shell syntax such as pipes or redirection, call `sh` with `['-c', script]`. Avoid building shell scripts from untrusted input.

```typescript
import type { Sandbox } from "@trickest/sandbox";

export async function readRuntimeVersion(sandbox: Sandbox): Promise<string> {
  const result = await sandbox.runCommand({
    cmd: "node",
    args: ["--version"],
    cwd: "/trickest/sandbox",
    timeoutMs: 30_000,
    maxOutputBytes: 64 * 1024,
  });
  if (result.exitCode !== 0) throw new Error(await result.stderr());
  if (result.truncated) throw new Error("Command output was truncated");
  return (await result.stdout()).trim();
}
```

`exitCode` is a property. `stdout()` and `stderr()` are asynchronous methods. A nonzero exit code does not automatically throw. The options form also accepts `env` for command-specific variables and `sudo` to run through sudo within the sandbox.

`timeoutMs` limits the client's wait for a synchronous command. It does not guarantee that the remote process stops. The default wait is unbounded, and detached commands ignore this option.

## Follow detached work

A detached handle lets you read logs, check completion, and signal the command. Work can continue after the initiating connection ends while the sandbox remains running.

```typescript
import type { Sandbox } from "@trickest/sandbox";

export async function followJob(sandbox: Sandbox): Promise<void> {
  const job = await sandbox.runCommand({
    cmd: "node",
    args: [
      "-e",
      'console.log("started"); setTimeout(() => console.log("finished"), 1000)',
    ],
    detached: true,
  });
  console.log("Command ID:", job.cmdId);
  for await (const text of job.logs()) process.stdout.write(text);
  const { exitCode } = await job.wait();
  if (exitCode !== 0) throw new Error(`Command exited with ${exitCode}`);
}
```

`logs()` is an async iterable. `wait()` resolves with an exit code and has no timeout or abort option. Use `job.kill()` to request SIGTERM, or pass a signal such as `'SIGKILL'`. A successful signal request is not proof of exit; check `wait()` or `sandbox.getCommand(job.cmdId)` afterward.

Stopping the sandbox ends its processes. Follow the [lifecycle guide](/docs/developer-tools/sandbox-sdk/lifecycle) to distinguish command completion from environment cleanup.

## Reconnect to incremental logs

Save the sandbox ID, command ID, and returned byte offsets if another request will continue reading. `readCommandLogs()` returns one chunk, not an async iterable.

```typescript
import type { Sandbox } from "@trickest/sandbox";

export async function readNextLogs(
  sandbox: Sandbox,
  cmdId: string,
  offsets = { stdoutFrom: 0, stderrFrom: 0 },
) {
  const chunk = await sandbox.readCommandLogs(cmdId, {
    ...offsets,
    idleMs: 500,
    maxMs: 2_000,
    maxBytesPerStream: 64 * 1024,
  });
  process.stdout.write(chunk.stdout);
  process.stderr.write(chunk.stderr);
  if (chunk.stdoutDropped || chunk.stderrDropped) {
    console.warn("Some command output is no longer retained");
  }
  return {
    stdoutFrom: chunk.stdoutOffset,
    stderrFrom: chunk.stderrOffset,
    finished: chunk.finished,
    exitCode: chunk.exitCode,
  };
}
```

Output retention is finite. Synchronous results expose `truncated`, `stdoutDropped`, and `stderrDropped`; incremental reads expose dropped-byte counts. Save required results to [files](/docs/developer-tools/sandbox-sdk/files) and download them before deleting the environment.

---
_Markdown source of https://trickest.com/docs/developer-tools/sandbox-sdk/commands._
