Launch and observe supervised processes in the current container for a sandbox.
For the mental model, refer to Process execution. For interactive PTY input and browser terminals, refer to Terminals and the Terminals API.
Process handles have no standard input. Use cwd, env, and argv (or an explicit shell script) for non-interactive work. Use a terminal when you need an interactive PTY.
Start a process from argv (executable, then arguments). Resolves when launch succeeds, not when the process exits. The SDK does not run a shell and does not shell-escape argv — each entry is one process argument.
exec(command: SandboxCommand, options?: ExecOptions): Promise<SandboxProcess>type SandboxCommand = readonly [executable: string, ...args: string[]];command[0]must be a non-empty executable path or name.- Later arguments may be empty strings.
- Entries are passed through as-is (no shell escaping of argv).
- Shell syntax requires an explicit shell, for example
['/bin/bash', '-lc', script].
| Field | Type | Description |
|---|---|---|
cwd |
string |
Working directory for this launch. Defaults to /workspace when unset. |
env |
Record<string, string> |
Environment overlay for this launch. Does not mutate later launches. Sandbox-level env still applies. |
timeout |
number |
Remote process lifetime in milliseconds. The supervisor may stop the process; completion can report timedOut: true. |
Promise<SandboxProcess>
const process = await sandbox.exec(["node", "--version"]);
const output = await process.output({ encoding: "utf8" });
console.log(process.id, process.pid, output.stdout, output.exitCode);const process = await sandbox.exec(["node", "--version"]);
const output = await process.output({ encoding: "utf8" });
console.log(process.id, process.pid, output.stdout, output.exitCode);Return a handle for a process running in the current container for this sandbox, or null.
Does not start a container if none is running. Returns null when no container is up, when the process ID is unknown in the current container, or when that process belonged to a previous container for the same sandbox ID.
getProcess(id: string): Promise<SandboxProcess | null>Process IDs are not durable across container stop or replace. Refer to How long a process lives.
List processes in the current container for this sandbox. Does not start a container if none is running. Returns an empty list when no container is up.
listProcesses(): Promise<ProcessStatus[]>Each entry is a ProcessStatus value (the same shape as status()).
| Member | Description |
|---|---|
id |
Process ID in the current container. |
pid |
Container pid at launch. |
exitCode |
Promise<number> that resolves when the supervised process group settles. |
status() |
Current discriminated status. |
logs(options?) |
Cursor-based log stream. |
output(options?) |
Buffered stdout and stderr plus exit metadata. |
waitForExit(options?) |
Wait until the supervised process group settles. |
waitForLog(pattern, options?) |
Wait until stdout or stderr matches. |
waitForPort(port, options?) |
Wait until a port is ready or readiness fails. |
kill(signal?) |
Send a numeric signal. Default 15 (SIGTERM). |
There is no process stdin API on this handle.
status(): Promise<ProcessStatus>Refer to ProcessStatus. A process stays running until the supervised process group has settled, even if the root pid exits while descendants continue.
Buffer stdout and stderr until the process completes (or the local wait ends), then return exit metadata.
output(options?: ProcessOutputOptions): Promise<ProcessOutput<Uint8Array>>
output(
options: ProcessOutputOptions & { encoding: "utf8" },
): Promise<ProcessOutput<string>>interface ProcessOutput<T = Uint8Array> {
stdout: T;
stderr: T;
exitCode: number;
signal?: number;
timedOut: boolean;
truncated: boolean;
}Default body encoding is binary (Uint8Array) unless you pass encoding: "utf8". Prefer logs() when output may exceed what you want to buffer.
| Field | Type | Description |
|---|---|---|
encoding |
"utf8" |
Decode stdout/stderr as strings. |
maxBytes |
number |
Cap buffered bytes per stream side of the result; may set truncated: true. No default cap when omitted. |
timeout |
number |
Local wait deadline in milliseconds only; does not kill the process. |
signal |
AbortSignal |
Cancel this wait only; does not kill the process. |
maxBytes must be a non-negative finite number when set.
const process = await sandbox.exec(["/bin/bash", "-lc", "npm test"], {
cwd: "/workspace/app",
});
const result = await process.output({ encoding: "utf8", timeout: 120_000 });
console.log(result.exitCode, result.stdout, result.timedOut, result.truncated);const process = await sandbox.exec(["/bin/bash", "-lc", "npm test"], {
cwd: "/workspace/app",
});
const result = await process.output({ encoding: "utf8", timeout: 120_000 });
console.log(result.exitCode, result.stdout, result.timedOut, result.truncated);Stream replayable log events with an opaque cursor.
logs(options?: ProcessLogsOptions): Promise<ReadableStream<ProcessLogEvent>>| Field | Type | Description |
|---|---|---|
since |
string |
Opaque cursor; resume after a previous event. |
replay |
boolean |
Include buffered history when resuming. |
follow |
boolean |
Keep the stream open for live output. |
signal |
AbortSignal |
Cancel this subscription only; the process keeps running. |
type ProcessLogEvent =
| {
type: "stdout" | "stderr";
cursor: string;
timestamp: string;
data: Uint8Array;
}
| {
type: "terminal";
state: "exited";
cursor: string;
timestamp: string;
exit: ProcessExit;
}
| {
type: "terminal";
state: "error";
cursor: string;
timestamp: string;
error: ProcessFailure;
}
| {
type: "truncated";
cursor?: string;
timestamp: string;
};Retain the latest cursor from delivered events if a later Worker request resumes with logs({ since: cursor, replay: true, follow: true }) on the same process in the same container.
const process = await sandbox.exec(["/bin/bash", "-lc", "npm run dev"], {
cwd: "/workspace/app",
});
const stream = await process.logs({ follow: true, replay: true });
const reader = stream.getReader();
const decoder = new TextDecoder();
for (;;) {
const { done, value } = await reader.read();
if (done) break;
if (value.type === "stdout" || value.type === "stderr") {
// Keep value.cursor if you will resume later
console.log(value.type, decoder.decode(value.data, { stream: true }));
continue;
}
if (value.type === "terminal") {
console.log("done", value.state);
break;
}
}const process = await sandbox.exec(["/bin/bash", "-lc", "npm run dev"], {
cwd: "/workspace/app",
});
const stream = await process.logs({ follow: true, replay: true });
const reader = stream.getReader();
const decoder = new TextDecoder();
for (;;) {
const { done, value } = await reader.read();
if (done) break;
if (value.type === "stdout" || value.type === "stderr") {
// Keep value.cursor if you will resume later
console.log(value.type, decoder.decode(value.data, { stream: true }));
continue;
}
if (value.type === "terminal") {
console.log("done", value.state);
break;
}
}Wait until the supervised process group settles.
waitForExit(options?: {
timeout?: number;
signal?: AbortSignal;
}): Promise<ProcessExit>| Field | Type | Description |
|---|---|---|
timeout |
number |
Local wait deadline only; does not kill the process. |
signal |
AbortSignal |
Cancel this wait only; does not kill the process. |
Returns ProcessExit. Local timeout surfaces as ProcessWaitTimeoutError. Local abort surfaces as ProcessAbortedError.
const build = await sandbox.exec(["/bin/bash", "-lc", "npm run build"], {
cwd: "/workspace/app",
});
const exit = await build.waitForExit({ timeout: 600_000 });
console.log(exit.code, exit.signal, exit.timedOut);const build = await sandbox.exec(["/bin/bash", "-lc", "npm run build"], {
cwd: "/workspace/app",
});
const exit = await build.waitForExit({ timeout: 600_000 });
console.log(exit.code, exit.signal, exit.timedOut);Wait until stdout and/or stderr matches a pattern.
waitForLog(
pattern: string | RegExp,
options?: WaitForLogOptions,
): Promise<WaitForLogResult>| Field | Type | Description |
|---|---|---|
stream |
"stdout" | "stderr" | "both" |
Which streams to match. Default: "both". |
timeout |
number |
Local wait deadline only; does not kill the process. |
signal |
AbortSignal |
Cancel this wait only; does not kill the process. |
interface WaitForLogResult {
stream: "stdout" | "stderr";
text: string;
match: string;
cursor?: string;
}textis the matching window of decoded output for that stream.matchis the matched substring.cursoris the log cursor at the match when available.
If the process exits before a match, the SDK throws ProcessExitedBeforeLogError. A local wait timeout throws ProcessWaitTimeoutError.
const server = await sandbox.exec(["/bin/bash", "-lc", "npm run dev"], {
cwd: "/workspace/app",
});
const ready = await server.waitForLog(/listening on/i, {
stream: "both",
timeout: 60_000,
});
console.log(ready.stream, ready.match);const server = await sandbox.exec(["/bin/bash", "-lc", "npm run dev"], {
cwd: "/workspace/app",
});
const ready = await server.waitForLog(/listening on/i, {
stream: "both",
timeout: 60_000,
});
console.log(ready.stream, ready.match);Wait until a port is ready, or fail if the process exits first or the local wait ends.
waitForPort(port: number, options?: WaitForPortOptions): Promise<void>| Field | Type | Description |
|---|---|---|
mode |
"tcp" | "http" |
Readiness check. Default: "tcp" (accepts a TCP connection). |
path |
string |
HTTP path to request when mode is "http". Default: "/". |
status |
number | { min: number; max: number } |
Expected HTTP status or inclusive range when mode is "http". Default: { min: 200, max: 399 }. |
interval |
number |
Milliseconds between checks. Default: 500. |
timeout |
number |
Local wait deadline only; does not kill the process. No default timeout when omitted. |
signal |
AbortSignal |
Cancel this wait only; does not kill the process. |
TCP mode (default) succeeds when the port accepts a connection:
const db = await sandbox.exec(["redis-server"]);
await db.waitForPort(6379, {
mode: "tcp",
timeout: 10_000,
});const db = await sandbox.exec(["redis-server"]);
await db.waitForPort(6379, {
mode: "tcp",
timeout: 10_000,
});HTTP mode issues an HTTP request and checks the response status:
const server = await sandbox.exec(["/bin/bash", "-lc", "npm run dev"], {
cwd: "/workspace/app",
});
await server.waitForPort(3000, {
mode: "http",
path: "/health",
status: { min: 200, max: 299 },
timeout: 60_000,
});const server = await sandbox.exec(["/bin/bash", "-lc", "npm run dev"], {
cwd: "/workspace/app",
});
await server.waitForPort(3000, {
mode: "http",
path: "/health",
status: { min: 200, max: 299 },
timeout: 60_000,
});Typical failures:
ProcessReadyTimeoutError— port not ready before the local timeoutProcessExitedBeforeReadyError— process exited before the port was readyProcessAbortedError— localAbortSignalcancelled the wait (process may still run)
Send a numeric signal to the process.
kill(signal?: number): Promise<void>Default signal is 15 (SIGTERM). Pass a numeric signal only (for example 9 for SIGKILL). String signal names are not accepted.
Stopping the process is separate from cancelling a local wait or log subscription.
readonly exitCode: Promise<number>Resolves to the exit code when the supervised process group has settled (the same completion boundary as waitForExit()). Prefer waitForExit() when you also need signal or timedOut.
type ProcessStatus =
| {
state: "running";
id: string;
pid: number;
command: SandboxCommand;
cwd?: string;
startedAt: string;
}
| {
state: "exited";
id: string;
pid: number;
command: SandboxCommand;
cwd?: string;
startedAt: string;
endedAt: string;
exit: ProcessExit;
}
| {
state: "error";
id: string;
pid: number;
command: SandboxCommand;
cwd?: string;
startedAt: string;
endedAt: string;
error: ProcessFailure;
};listProcesses() returns ProcessStatus[] using this shape.
Outcome observed for the root subprocess when the supervised group settles.
interface ProcessExit {
code: number;
signal?: number;
timedOut: boolean;
}Signals delivered only to descendants do not rewrite this outcome. Refer to Process execution.
interface ProcessFailure {
code: string;
message: string;
}getProcess and listProcesses do not throw for missing work. They return null or [] when no container is up, the ID is unknown in the current container, or the process belonged to a previous container. The following error classes apply to operations on a process handle (and to launch), not to those lookups.
| Situation | Class / outcome |
|---|---|
getProcess / listProcesses while no container is running |
null / [] (not an error; does not start a container) |
getProcess for an unknown ID or a process from a previous container |
null |
| Operation on a handle after the container was replaced | StaleProcessHandleError |
| Operation on a handle when the process is gone in the current container | ProcessNotFoundError |
Local wait timed out (output / waitForExit / waitForLog) |
ProcessWaitTimeoutError |
Local AbortSignal on a wait or stream |
ProcessAbortedError |
| Port not ready before local timeout | ProcessReadyTimeoutError |
| Process exited before port readiness | ProcessExitedBeforeReadyError |
| Process exited before a log match | ProcessExitedBeforeLogError |
| Invalid working directory at launch | InvalidProcessCwdError |
| Invalid environment at launch | InvalidProcessEnvironmentError |
| Invalid log cursor | InvalidProcessCursorError |
| Process failed to start | ProcessSpawnFailedError |
| Container not ready; work did not start | ContainerUnavailableError |
| Work interrupted after it may have started | OperationInterruptedError |
Recovery guidance: Errors and recovery. Full catalog: Errors API. Lifetime: How long a process lives.