Processes
Run, stream, and manage sandbox processes with @tektona/sdk — run vs start, handles, waitForPort, and typed errors.
sandbox.process runs commands inside a sandbox and hands back sandbox-owned
handles: a process survives client disconnects and can be re-fetched later by
ULID or name. Get the namespace off any Sandbox instance:
const sandbox = await tek.sandbox.get('01J9F3ZK7QW4B2Y8N5T1QAZ6RX');
sandbox.process; // ProcessServicerun vs start
run executes a one-shot command to completion and returns its buffered
result. It waits by streaming under the hood — no wait ceiling:
const r = await sandbox.process.run('npm test', {
env: { CI: 'true' },
cwd: '/workspace',
timeoutSeconds: 600, // kill the whole group after 10 min
});
r.exitCode; // number (128 + signal number when signal-killed)
r.stdout; // string
r.stderr; // string
r.signal; // string | undefinedThe command is a shell command line — &&, pipes, redirection, and globs work
directly (sandbox.process.run('npm ci && npm test 2>&1')). This differs from
the CLI's process run, which takes an argument vector unless given
-s/--shell.
start launches a background process and returns a ProcessHandle you keep
using while the process runs on the server:
const dev = await sandbox.process.start('npm run dev', {
name: 'dev-server',
preventAutoPause: true, // keep the sandbox awake while it runs
onHibernate: 'restart_after_resume', // 'preserve' | 'stop' | 'restart_after_resume'
autostart: true, // persist the definition; relaunch on every boot
});Start options
Both run and start accept the same options:
| Option | Type | Notes |
|---|---|---|
name | string | [a-z0-9_-], unique among running processes. A memorable name is generated when omitted. |
env | Record<string, string> | Environment variables. |
cwd | string | Working directory. |
user | string | Run-as Linux user; defaults to the image's default user. |
tty | { cols, rows } | Allocate a PTY at this size (resizable later). Absent = pipe mode. |
preventAutoPause | boolean | Keep the sandbox awake while the process runs. |
onHibernate | 'preserve' | 'stop' | 'restart_after_resume' | Behavior across a hibernate pause (a disk-only suspend cold-boots the guest — use autostart to relaunch there). |
autostart | boolean | Persist the definition and relaunch on every boot; requires name. |
timeoutSeconds | number | Kill the whole process group after this many seconds. |
maxLogBytes | number | On-disk log cap. Omit for the sandbox default; an explicit 0 disables logging (0 and omitted differ). |
The handle
start, getById, getByName, and list all return ProcessHandles. A
handle carries a snapshot of the process — id, name, command, pid,
status (running | exited | killed | failed), tty, exitCode,
terminatingSignal, finishReason, preventAutoPause, onHibernate,
autostart, cwd, user, maxLogBytes, startedAt, finishedAt, error,
restartedFrom — plus methods to drive it.
Logs
// buffered fetch (default)
for await (const ev of dev.logs()) {
process.stdout.write(ev.data); // ev: { seq, stream: 'stdout'|'stderr'|'tty', ts, data }
}
// live tail over a WebSocket until the process ends
for await (const ev of dev.logs({ follow: true, tail: 100 })) {
process.stdout.write(ev.data);
}logs() throws LoggingDisabledError if the process ran with maxLogBytes: 0.
Wait, stop, signal
const code = await dev.wait(); // resolves with the exit code (streams live)
await dev.stop(); // TERM → grace → KILL (whole group)
await dev.stop({ force: true }); // immediate SIGKILL
await dev.signal('SIGHUP'); // deliver one signal to the group
await dev.setAutostart(false); // toggle the persisted autostart definitionAttach (interactive)
attach opens a read-write session and returns live controls. onData receives
output as it arrives:
const t = await shell.attach({ onData: (bytes) => term.write(bytes) });
t.write('ls\n'); // string or Uint8Array stdin
t.resize({ cols: 200, rows: 50 });
t.signal('SIGINT');
t.detach(); // leave the session — the process keeps runningwaitForPort
A client-side helper that resolves once a port is listening inside the sandbox — ideal for "wait for the dev server to come up". It polls the sandbox's listening-ports endpoint while watching the process, so a crash fails fast instead of waiting out the timeout:
const dev = await sandbox.process.start('npm run dev', { name: 'dev-server' });
await dev.waitForPort(3000, { timeoutMs: 30_000, intervalMs: 500 });
// port 3000 is now listeningIf the process exits before the port appears, it rejects immediately with
ProcessExitedError (carrying exitCode, signal, and a tail of stderr). On
timeout it rejects with TimeoutError and leaves the process running — the
helper never stops anything. It adds no server state; CLI and raw-HTTP users can
replicate it by combining the ports and logs endpoints.
Addressing processes
Re-attach from a fresh client with no handle bookkeeping:
const byName = await sandbox.process.getByName('dev-server'); // running, else most-recent finished
const byId = await sandbox.process.getById('01J9F3ZK7QW4B2Y8N5T1QAZ6RX');
const all = await sandbox.process.list();
const defs = await sandbox.process.list({ autostart: true }); // include persisted definitionsgetByName resolves a name to its running holder, or — if none is running — to
the most-recent finished process with that name (names are reusable). Enable or
disable a persisted autostart definition without a handle via
sandbox.process.setAutostart(ref, enabled).
Errors
| Error | When |
|---|---|
ProcessNotFoundError | Unknown ref — an unknown ULID, or a name with no running or finished holder (404). |
ConflictError | Name collision, the per-sandbox process cap, or a process op on a paused sandbox (409). |
LoggingDisabledError | logs() / log stream on a process started with maxLogBytes: 0 (409). |
ProcessExitedError | waitForPort — the process exited before the port started listening. Carries exitCode, signal, stderr. |
TimeoutError | waitForPort — the port didn't appear within timeoutMs. The process is left running. |
See also
- Processes guide — concepts and CLI walkthrough
- Process streaming protocol — the underlying WebSocket wire format