Skip to content
ai-sandbox-local
Esc
↑↓navigate↵open⌘Jpreview
On this page

Sessions and processes

Running commands, the leased port, and how a session shuts down.

The harness drives the sandbox for you, so most code never touches this page. It is for when you use the session directly: running a command next to the agent, handing a tool a safe view of the sandbox, or working out what survives shutdown.

A LocalNetworkSandboxSession is a directory plus one leased loopback port. It is also a full AI SDK sandbox session, so it has run, spawn, and file I/O.

Run and spawn

Both run command with /bin/bash -c in the session’s working directory, or in workingDirectory when you pass one. They take the same options:

const result = await sandboxSession.run({
	command: 'git status --short',
	workingDirectory: '/path/to/your/repo/packages/app',
	env: { NO_COLOR: '1' },
	abortSignal: AbortSignal.timeout(10_000),
});

console.log(result.exitCode, result.stdout, result.stderr);

run waits for the command and returns its output as strings. spawn returns right away with a handle: stdout and stderr are web ReadableStreams, wait() resolves with the exit code, and kill() ends the process.

const proc = await sandboxSession.spawn({ command: 'sleep 60' });

await proc.kill();
const exit = await proc.wait(); // { exitCode: 143 }

Things that are easy to trip on:

  • A failing command doesn’t reject. run resolves with the command’s exit code, so command: 'exit 3' gives exitCode: 3, and a missing program gives 127 with bash’s error in stderr. Only failing to start the shell, or an abort, rejects.
  • Stdin is closed. A command that reads input gets end-of-file immediately.
  • A missing workingDirectory looks like a missing bash. The call rejects with spawn /bin/bash ENOENT, because that is what Node reports for a bad cwd.
  • A killed process reports 128 + signal. kill() sends SIGTERM to the process and its current descendants, so the exit code is 143, the same value a shell shows in $?.
  • Aborting kills the process tree and rejects. Pass an abortSignal and both run and spawn().wait() reject with an AbortError. An already-aborted signal rejects before anything starts.

File helpers such as readTextFile and writeTextFile resolve a relative path against the session’s working directory. They do not confine paths to it: an absolute path or .. reaches anywhere your user can. readTextFile returns null for a missing file, and the write helpers create parent directories.

Ports

Each session leases one free loopback port when it is created, and ports lists it. Harnesses with a bridge bind their server to it. Sessions created in the same process never share a port. The lease is not coordinated between processes, so two Node processes can be handed the same number.

The package only reserves the number. It doesn’t bind anything, and getPortEndpoint doesn’t check that anything is listening:

console.log(sandboxSession.ports); // [60479]

await sandboxSession.getPortEndpoint({ port: 60479, protocol: 'ws' });
// { url: 'ws://127.0.0.1:60479' }

protocol defaults to http. The deprecated getPortUrl returns the same URL as a string.

Restricted view

restricted() returns a reduced view of the session: run, spawn, and file I/O in the same directory with the same env, and none of stop, destroy, or the port methods. Hand this to code that should be able to run commands but not end the session. The harness passes this view, never the full session, to the code it hands the sandbox to.

const tools = sandboxSession.restricted();
await tools.run({ command: 'ls' });

Stop and destroy

On a local session stop() and destroy() do the same thing, and calling either twice is safe. Both:

  • send SIGTERM to every process still running that was started through the session or a restricted() view of it, whether with spawn() or run(), and to all of their current descendants. That includes the harness’s bridge and the CLI server it launched while the bridge is still running. A process whose parent has already exited has been reparented to PID 1 and can’t be found. A run in flight returns with exit code 143;
  • release the leased port.

They send the signal and return without waiting for the processes to exit. Neither deletes or empties the directory. It is your real checkout.

Processes started after stop() are not tracked or killed. End the harness session first with session.destroy(), then the sandbox with sandboxSession.destroy(), as the quickstart does, so the harness can shut down its bridge cleanly before the signal arrives.

Was this page helpful?