TypeScript SDK

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; // ProcessService

run 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 | undefined

The 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:

OptionTypeNotes
namestring[a-z0-9_-], unique among running processes. A memorable name is generated when omitted.
envRecord<string, string>Environment variables.
cwdstringWorking directory.
userstringRun-as Linux user; defaults to the image's default user.
tty{ cols, rows }Allocate a PTY at this size (resizable later). Absent = pipe mode.
preventAutoPausebooleanKeep 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).
autostartbooleanPersist the definition and relaunch on every boot; requires name.
timeoutSecondsnumberKill the whole process group after this many seconds.
maxLogBytesnumberOn-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 definition

Attach (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 running

waitForPort

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 listening

If 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 definitions

getByName 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

ErrorWhen
ProcessNotFoundErrorUnknown ref — an unknown ULID, or a name with no running or finished holder (404).
ConflictErrorName collision, the per-sandbox process cap, or a process op on a paused sandbox (409).
LoggingDisabledErrorlogs() / log stream on a process started with maxLogBytes: 0 (409).
ProcessExitedErrorwaitForPort — the process exited before the port started listening. Carries exitCode, signal, stderr.
TimeoutErrorwaitForPort — the port didn't appear within timeoutMs. The process is left running.

See also

On this page