Skip to content

Running Code

Running code is the core operation in Runra Sandbox. This guide covers everything from basic command execution to advanced patterns like streaming output and error recovery.

The exec() method runs a shell command inside the sandbox:

const result = await sandbox.exec("echo 'Hello, world!'");
console.log(result.stdout); // "Hello, world!\n"
console.log(result.stderr); // ""
console.log(result.exitCode); // 0

Every exec() call returns an ExecutionResult:

interface ExecutionResult {
stdout: string; // Standard output
stderr: string; // Standard error
exitCode: number; // Process exit code (0 = success)
durationMs: number; // Execution time in milliseconds
signal: string | null; // Signal that killed the process, if any
}

Non-zero exit codes indicate errors. Always check exitCode:

const result = await sandbox.exec("npm test");
if (result.exitCode !== 0) {
console.error(`Tests failed with exit code ${result.exitCode}`);
console.error(result.stderr);
// Handle failure — maybe notify, retry, or abort
}
CodeMeaningResponse
0SuccessContinue
1General errorCheck stderr for details
2Misuse of shell builtinsCheck command syntax
126Command invoked cannot executeCheck permissions
127Command not foundInstall missing tool
130Script terminated by Ctrl+CHandle interruption
137Killed (SIGKILL)Usually OOM — increase memory
143Terminated (SIGTERM)Graceful shutdown signal

For long-running commands, stream stdout and stderr in real time:

const stream = await sandbox.exec("npm run build", {
onStdout: (line: string) => {
console.log(`[stdout] ${line}`);
// Parse build progress, update UI, etc.
},
onStderr: (line: string) => {
console.warn(`[stderr] ${line}`);
// Track warnings
},
});
// Stream also returns the final ExecutionResult
console.log(`Build completed with exit code ${stream.exitCode}`);

By default, stdout and stderr are fully buffered. For commands that produce a lot of output, use streaming to avoid memory issues:

// For a command that generates GBs of logs
const lines: string[] = [];
await sandbox.exec("find / -type f", {
onStdout: (line) => {
// Process incrementally instead of buffering everything
if (line.includes("node_modules")) return;
lines.push(line);
if (lines.length > 10000) {
lines.splice(0, 5000); // Keep a sliding window
}
},
});

Pass environment variables to sandbox commands:

// Set per-command environment
const result = await sandbox.exec("python train.py", {
env: {
MODEL_NAME: "gpt-tiny",
EPOCHS: "50",
BATCH_SIZE: "32",
WANDB_API_KEY: process.env.WANDB_API_KEY!,
},
});

Set environment variables for the entire sandbox lifecycle:

const sandbox = await runra.sandboxes.create({
image: "node:22",
env: {
NODE_ENV: "production",
DATABASE_URL: "postgres://...",
// These are available to all subsequent exec() calls
},
resources: { cpu: 2, memoryMb: 4096 },
});
// No need to pass NODE_ENV again
const result = await sandbox.exec("node app.js");

Never hardcode secrets. Use runtime injection:

// ✅ Good: Secrets from environment or secret manager
await sandbox.exec("deploy.sh", {
env: {
CLOUDFLARE_API_TOKEN: await secrets.get("CLOUDFLARE_API_TOKEN"),
GITHUB_TOKEN: await secrets.get("GITHUB_TOKEN"),
},
});
// ❌ Bad: Hardcoded secrets
await sandbox.exec("deploy.sh", {
env: {
CLOUDFLARE_API_TOKEN: "abc123-hardcoded-bad",
},
});

Specify the working directory for commands:

await sandbox.exec("git clone https://github.com/acme/app.git");
await sandbox.exec("npm install", { cwd: "/workspace/app" });
await sandbox.exec("npm test", { cwd: "/workspace/app" });

The default working directory is /workspace. You can change it per-command or set a sandbox-level default:

const sandbox = await runra.sandboxes.create({
image: "node:22",
workdir: "/workspace/my-project", // Default for all commands
});

Set timeouts to prevent runaway commands:

// Per-command timeout (5 seconds)
const result = await sandbox.exec("curl https://slow-api.example.com", {
timeoutMs: 5_000,
});
if (result.exitCode === null && result.signal === "SIGKILL") {
console.log("Command timed out");
}

When a timeout fires:

  1. The process inside the sandbox is sent SIGTERM
  2. If it doesn’t exit within 5 seconds, SIGKILL is sent
  3. exitCode is null and signal is the signal that killed it
  4. The sandbox remains running — only the command is killed

Set a maximum lifetime for the entire sandbox:

const sandbox = await runra.sandboxes.create({
image: "node:22",
timeoutMs: 600_000, // Terminate sandbox after 10 minutes
});
// Write a script
await sandbox.files.write("/workspace/setup.sh", `#!/bin/bash
set -e
npm install
npm run build
npm test
`);
// Make it executable and run
await sandbox.exec("chmod +x /workspace/setup.sh");
const result = await sandbox.exec("/workspace/setup.sh");
Node.js
// Python
await sandbox.exec("python -c 'print(sum(range(100)))'");
await sandbox.exec('node -e "console.log(process.version)"');
// Shell pipeline
const result = await sandbox.exec("cat package.json | grep version | cut -d'\"' -f4");
console.log(result.stdout.trim()); // e.g., "1.0.0"

Start background processes with &:

// Start a dev server in the background
await sandbox.exec("npm run dev &");
// Wait for the server to be ready
await sandbox.exec("sleep 3 && curl -s http://localhost:3000/health");
// The server keeps running — you can make more exec() calls
const result = await sandbox.exec("curl -s http://localhost:3000/api/users");
// Find background processes
const ps = await sandbox.exec("ps aux");
console.log(ps.stdout);
// Kill a specific process
await sandbox.exec("pkill -f 'node server.js'");
// Kill all background node processes
await sandbox.exec("pkill node");
try {
await sandbox.exec(`node -e "throw new Error('boom')"`);
} catch (error) {
// Non-zero exit codes don't throw by default
// Only infrastructure errors throw
console.error("Infrastructure error:", error.message);
}
async function runOrThrow(sandbox: Sandbox, command: string) {
const result = await sandbox.exec(command);
if (result.exitCode !== 0) {
throw new Error(
`Command failed (exit ${result.exitCode}): ${command}\n${result.stderr}`
);
}
return result;
}
// Usage
await runOrThrow(sandbox, "npm test"); // Throws if tests fail
async function execWithRetry(
sandbox: Sandbox,
command: string,
maxRetries = 3
): Promise<ExecutionResult> {
for (let i = 0; i < maxRetries; i++) {
const result = await sandbox.exec(command);
if (result.exitCode === 0) return result;
// Only retry on specific failures (e.g., network timeouts)
if (result.stderr.includes("ETIMEDOUT") && i < maxRetries - 1) {
console.warn(`Retrying (${i + 1}/${maxRetries}): ${command}`);
await new Promise((r) => setTimeout(r, 2000));
continue;
}
return result; // Don't retry other failures
}
throw new Error(`Command failed after ${maxRetries} retries: ${command}`);
}

Chain multiple commands together:

// Sequential commands (each waits for the previous)
await sandbox.exec("git clone https://github.com/acme/app.git");
await sandbox.exec("cd /workspace/app && npm install");
const result = await sandbox.exec("cd /workspace/app && npm test");
// Or in a single shell call with &&
const result = await sandbox.exec(
"git clone https://github.com/acme/app.git && cd app && npm install && npm test"
);
// Run commands conditionally based on previous results
const check = await sandbox.exec("test -d /workspace/app");
if (check.exitCode === 0) {
await sandbox.exec("cd /workspace/app && git pull");
} else {
await sandbox.exec("git clone https://github.com/acme/app.git");
}