Timeouts & cancellation
Sandbox lifetime limits, bash-level command timeouts, and cleanup with try/finally.
Sandbox lifetime
Set a maximum lifetime for the entire sandbox container via SandboxOptions.timeout (in seconds). The OpenSandbox server terminates the container after this duration:
const sb = await client.sandbox({
image: "ubuntu:22.04",
resources: { cpu: "500m", memory: "256Mi" },
timeout: 300, // terminate after 5 minutes
});This is a hard limit on the container — not on individual commands.
Per-command timeout via bash
Use the bash timeout command to limit how long a single command can run. The timeout utility exits with code 124 if the limit is reached:
const sb = await client.sandbox({
image: "ubuntu:22.04",
resources: { cpu: "500m", memory: "256Mi" },
});
try {
// Times out after 1 second, exits with 124
const { exitCode } = await sb.exec("timeout 1 sleep 30 || echo 'timed out'", { strict: false });
console.log(`exit code: ${exitCode}`); // 0 (the || echo masked it)
} finally {
await sb.close();
}To get the actual timeout exit code:
const { exitCode } = await sb.exec("timeout 10 long-running-command", { strict: false });
if (exitCode === 124) {
console.log("command timed out");
} else if (exitCode !== 0) {
console.log(`command failed with ${exitCode}`);
}Cleanup with try/finally
Always wrap sandbox usage in try/finally to ensure sb.close() runs even if an exec throws or a timeout fires:
const sb = await client.sandbox({
image: "ubuntu:22.04",
resources: { cpu: "500m", memory: "512Mi" },
});
try {
await sb.exec("step 1").pipe(process.stdout);
await sb.exec("step 2").pipe(process.stdout);
// If step 2 throws CommandError, finally still runs
} finally {
await sb.close(); // always deletes the container
}close() is idempotent — calling it multiple times is safe.
Handling long-running commands
For commands that might run indefinitely, combine timeout with error handling:
const sb = await client.sandbox({
image: "ubuntu:22.04",
resources: { cpu: "500m", memory: "256Mi" },
});
try {
const result = await sb.exec("timeout 60 ./run-tests.sh", { strict: false });
if (result.exitCode === 124) {
console.error("Tests timed out after 60 seconds");
} else if (result.exitCode !== 0) {
console.error(`Tests failed: exit ${result.exitCode}`);
console.error(result.stderr);
}
} finally {
await sb.close();
}Retry with backoff
For flaky operations, use retry from @alineo-labs/workflow instead of manual retry loops. It handles backoff and re-runs cleanly:
import { workflow } from "@alineo-labs/workflow";
await workflow(client)
.sandbox({ image: "ubuntu:22.04", resources: { cpu: "500m", memory: "256Mi" } }, (sb) => {
sb.retry(5, (sb) => sb.exec("curl --retry 0 --max-time 10 https://example.com"), {
delayMs: 1000,
backoff: "exponential",
});
})
.pipe(process.stdout);See Control flow for full retry docs.