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.
runresolves with the command’s exit code, socommand: 'exit 3'givesexitCode: 3, and a missing program gives127with bash’s error instderr. 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
workingDirectorylooks like a missing bash. The call rejects withspawn /bin/bash ENOENT, because that is what Node reports for a badcwd. - A killed process reports
128 + signal.kill()sendsSIGTERMto the process and its current descendants, so the exit code is143, the same value a shell shows in$?. - Aborting kills the process tree and rejects. Pass an
abortSignaland bothrunandspawn().wait()reject with anAbortError. 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
SIGTERMto every process still running that was started through the session or arestricted()view of it, whether withspawn()orrun(), 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. Arunin 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.