Skip to main content

Overview

Sessions provide isolated execution contexts within a sandbox, allowing you to maintain separate working directories, environment variables, and shell state across multiple command executions.

Session architecture

Default session

Every sandbox automatically creates a default session when you execute your first command:
const sandbox = getSandbox(env.SANDBOX, 'my-sandbox');

// Uses default session automatically
await sandbox.exec('echo "Hello World"');
await sandbox.exec('pwd'); // Working directory persists

Multiple sessions

Create multiple sessions to isolate different workflows:
// Create a build session
const buildSession = await sandbox.createSession({
  id: 'build-session',
  cwd: '/workspace/app'
});

// Create a test session
const testSession = await sandbox.createSession({
  id: 'test-session',
  cwd: '/workspace/tests'
});

// Commands in different sessions are isolated
await buildSession.exec('npm run build');
await testSession.exec('npm test');

State persistence

Working directory

The current working directory persists across commands within the same session:
const session = await sandbox.createSession();

// Change directory
await session.exec('cd /tmp');

// Directory persists
const result = await session.exec('pwd');
console.log(result.stdout); // "/tmp"

Environment variables

Environment variables set in a session persist for that session:
await session.exec('export API_KEY=secret123');
await session.exec('export NODE_ENV=production');

// Variables are available in subsequent commands
await session.exec('echo $API_KEY'); // "secret123"
Session state is maintained by running commands in a persistent bash shell using group commands ({ cmd }) rather than subshells. This ensures cd, export, and shell functions affect the session’s environment.

Initial environment

Set environment variables when creating a session:
const session = await sandbox.createSession({
  id: 'api-session',
  env: {
    API_KEY: 'secret123',
    NODE_ENV: 'production',
    LOG_LEVEL: 'debug'
  }
});

// Variables are immediately available
await session.exec('echo $NODE_ENV'); // "production"

Execution modes

The SDK uses two distinct execution patterns based on your needs:

Foreground execution

Foreground commands run synchronously and preserve session state:
// State-changing commands run in foreground
await session.exec('cd /workspace && npm install');
await session.exec('export PATH=$PATH:/usr/local/bin');

// Next command sees the updated state
await session.exec('which custom-tool');
Characteristics:
  • Blocks until command completes
  • Preserves working directory and environment
  • Returns complete stdout/stderr after execution
  • Suitable for sequential operations

Background execution

Background processes run asynchronously and stream output:
// Start a long-running server
const process = await sandbox.startProcess({
  command: 'npm run dev',
  sessionId: 'dev-session'
});

// Stream logs in real-time
for await (const log of process.logs()) {
  console.log(log.output);
}
Characteristics:
  • Non-blocking, returns immediately after start
  • Streams output in real-time
  • State changes don’t persist to session
  • Suitable for servers and long-running tasks
Background processes run in a subshell, so state changes (like cd or export) don’t affect the parent session.

Session serialization

Command ordering

Commands within the same session execute sequentially using a mutex:
// These commands run one at a time in order
const promise1 = session.exec('npm install');
const promise2 = session.exec('npm run build');
const promise3 = session.exec('npm test');

// All three are queued and execute in order
await Promise.all([promise1, promise2, promise3]);
This prevents race conditions when commands depend on session state.

Cross-session parallelism

Commands in different sessions can run in parallel:
const frontend = await sandbox.createSession({ id: 'frontend' });
const backend = await sandbox.createSession({ id: 'backend' });

// These run simultaneously
await Promise.all([
  frontend.exec('npm run build'),
  backend.exec('npm run build')
]);

Session lifecycle

Creating sessions

Explicitly create a session with custom options:
const session = await sandbox.createSession({
  id: 'my-session',
  cwd: '/workspace/project',
  env: {
    NODE_ENV: 'development'
  }
});

Retrieving sessions

Get an existing session by ID:
const session = await sandbox.getSession('my-session');

// Check if it exists
try {
  const session = await sandbox.getSession('unknown');
} catch (error) {
  console.error('Session not found');
}

Listing sessions

Retrieve all active sessions:
const sessions = await sandbox.listSessions();

for (const session of sessions) {
  console.log(`Session ${session.id} at ${session.cwd}`);
}

Deleting sessions

Clean up sessions when done:
// Delete a specific session
await sandbox.deleteSession('my-session');

// Delete all sessions
const sessions = await sandbox.listSessions();
for (const session of sessions) {
  await sandbox.deleteSession(session.id);
}
The default session cannot be deleted. Attempting to delete it will have no effect.

Shell execution internals

Understanding the implementation helps debug unexpected behavior:

Foreground mechanism

Foreground commands use temporary files for reliable output capture:
# Simplified version of what happens internally
{
  cd "$cwd" || exit 1
  { command } > stdout.tmp 2> stderr.tmp
  echo $? > exitcode
}

Background mechanism

Background processes use named pipes (FIFOs) for real-time streaming:
# Simplified version
mkfifo stdout.pipe stderr.pipe
(
  { command } > stdout.pipe 2> stderr.pipe &
  echo $! > pid
)

Completion detection

Commands signal completion through atomic file operations:
  1. Write exit code to temporary file
  2. Atomic rename to signal completion
  3. SDK detects via fs.watch + polling fallback
This hybrid approach handles unreliable filesystem events on tmpfs/overlayfs.

Best practices

Use sessions for isolation

Create separate sessions for logically independent workflows:
// Good: Isolated environments
const dev = await sandbox.createSession({ id: 'dev', cwd: '/app' });
const prod = await sandbox.createSession({ id: 'prod', cwd: '/app' });

// Bad: Mixing environments in one session
await session.exec('NODE_ENV=dev npm start');
await session.exec('NODE_ENV=prod npm start');
Use && to chain dependent commands in a single execution:
// Good: Single execution, atomic operation
await session.exec('cd /workspace && npm install && npm run build');

// Also good but slower: Separate executions
await session.exec('cd /workspace');
await session.exec('npm install');
await session.exec('npm run build');

Clean up temporary sessions

Delete sessions after one-time operations:
try {
  const tempSession = await sandbox.createSession({
    id: 'temp-analysis',
    cwd: '/tmp'
  });
  
  await tempSession.exec('analyze-data.sh');
} finally {
  await sandbox.deleteSession('temp-analysis');
}

Monitor session count

Limit concurrent sessions to avoid resource exhaustion:
const MAX_SESSIONS = 10;

const sessions = await sandbox.listSessions();
if (sessions.length >= MAX_SESSIONS) {
  // Clean up oldest sessions
  await sandbox.deleteSession(sessions[0].id);
}

Common patterns

Per-user sessions

Isolate user workspaces:
const userId = 'user-123';
const userSession = await sandbox.createSession({
  id: `user-${userId}`,
  cwd: `/workspace/users/${userId}`
});

Build pipelines

Create sessions for each pipeline stage:
const stages = ['build', 'test', 'deploy'];

for (const stage of stages) {
  const session = await sandbox.createSession({
    id: `pipeline-${stage}`,
    cwd: '/workspace'
  });
  
  await session.exec(`npm run ${stage}`);
  await sandbox.deleteSession(`pipeline-${stage}`);
}

Parallel test execution

Run test suites in parallel sessions:
const testFiles = ['auth.test.js', 'api.test.js', 'db.test.js'];

await Promise.all(
  testFiles.map(async (file) => {
    const session = await sandbox.createSession({
      id: `test-${file}`
    });
    return session.exec(`npm test -- ${file}`);
  })
);

Troubleshooting

Session not found errors

Verify session exists before using:
try {
  const session = await sandbox.getSession('my-session');
  await session.exec('pwd');
} catch (error) {
  if (error.code === 'SESSION_NOT_FOUND') {
    // Create session if it doesn't exist
    await sandbox.createSession({ id: 'my-session' });
  }
}

State not persisting

Ensure you’re using the same session:
// Wrong: Creates new default session each time
await sandbox.exec('cd /tmp');
await sandbox.exec('pwd'); // Might not be /tmp

// Right: Explicit session reference
const session = await sandbox.getSession('default');
await session.exec('cd /tmp');
await session.exec('pwd'); // Always /tmp

Shell exits unexpectedly

Avoid commands that terminate the shell:
// Don't do this - kills the session shell
await session.exec('exit');

// Use process management instead
const proc = await sandbox.startProcess({ command: 'server' });
await sandbox.killProcess(proc.id);

Build docs developers (and LLMs) love