docs/sdk/errors.mdx
All SDKs surface typed errors so you can match on specific failure modes instead of parsing strings. Rust has an Error enum, TypeScript exposes a dedicated subclass per variant (use instanceof), Python provides dedicated exception classes, and Go provides an *Error value with an ErrorKind discriminator matched via m.IsKind(err, kind) or errors.As.
async fn get_or_create(name: &str) -> Result<Sandbox, Error> { match Sandbox::get(name).await { Ok(handle) => handle.start().await, Err(Error::SandboxNotFound(_)) => { Sandbox::builder(name).image("python").create().await } Err(e) => Err(e), } }
match sb.exec("python", ["script.py"]).await { Ok(output) if output.status().success => { println!("{}", output.stdout()?); } Ok(output) => { eprintln!("Exit {}: {}", output.status().code, output.stderr()?); } Err(Error::ExecTimeout) => eprintln!("Timed out"), Err(Error::Runtime(msg)) => eprintln!("Runtime: {msg}"), Err(e) => return Err(e), }
```typescript TypeScript
import {
ExecTimeoutError,
RuntimeError,
Sandbox,
SandboxNotFoundError,
} from "microsandbox";
async function getOrCreate(name: string): Promise<Sandbox> {
try {
const handle = await Sandbox.get(name);
return await handle.start();
} catch (e) {
if (e instanceof SandboxNotFoundError) {
return Sandbox.builder(name).image("python").create();
}
throw e;
}
}
try {
const output = await sb.exec("python", ["script.py"]);
if (!output.success) {
console.error(`Failed (exit ${output.code}):`, output.stderr());
}
} catch (e) {
if (e instanceof ExecTimeoutError) {
console.error(`Timed out after ${e.timeoutMs}ms`);
} else if (e instanceof RuntimeError) {
console.error("Runtime:", e.message);
} else {
throw e;
}
}
from microsandbox import (
ExecTimeoutError, Sandbox, SandboxNotFoundError
)
async def get_or_create(name: str):
try:
handle = await Sandbox.get(name)
return await handle.start()
except SandboxNotFoundError:
return await Sandbox.create(name, image="python")
try:
output = await sb.exec("python", ["script.py"])
if not output.success:
print(f"Exit {output.exit_code}: {output.stderr_text}")
except ExecTimeoutError:
print("Timed out")
import (
"context"
"errors"
"log"
m "github.com/superradcompany/microsandbox/sdk/go"
)
func getOrCreate(ctx context.Context, name string) (*m.Sandbox, error) {
handle, err := m.GetSandbox(ctx, name)
if err != nil {
if m.IsKind(err, m.ErrSandboxNotFound) {
return m.CreateSandbox(ctx, name,
m.WithImage("python"))
}
return nil, err
}
return handle.Start(ctx)
}
out, err := sb.Exec(ctx, "python", []string{"script.py"})
switch {
case err == nil && !out.Success():
log.Printf("exit %d: %s", out.ExitCode(), out.Stderr())
case m.IsKind(err, m.ErrExecTimeout):
log.Println("timed out")
case err != nil:
// errors.As for deeper inspection.
var me *m.Error
if errors.As(err, &me) {
log.Printf("kind=%s message=%s", me.Kind, me.Message)
}
}
exec() distinguishes between:
ExecOutput with a non-zero code. This is not an error in the SDK sense; it's a normal result.ExecFailed (Rust), ExecFailedError (TypeScript), ExecFailedError (Python).The typed error carries a classified kind plus the underlying errno, so callers can branch on the cause and react. Common kinds: NotFound (binary missing on PATH), PermissionDenied, NotExecutable, BadCwd, BadArgs, ResourceLimit, UserSetupFailed, OutOfMemory, PtySetupFailed, Other.
match sb.exec("nonexistent", []).await { Ok(output) => { /* program ran, check output.status() */ } Err(Error::ExecFailed(payload)) => { match payload.kind { ExecFailureKind::NotFound => { eprintln!("Binary not found on PATH: {}", payload.message); } ExecFailureKind::PermissionDenied => { eprintln!("Not executable (chmod +x?): {}", payload.message); } kind => { eprintln!("Spawn failed ({:?}): {}", kind, payload.message); } } // payload.errno, payload.errno_name, payload.stage are also available } Err(e) => return Err(e), }
```typescript TypeScript
import { ExecFailedError, Sandbox } from "microsandbox";
try {
const output = await sb.exec("nonexistent");
// program ran, check output.success / output.code
} catch (e) {
if (e instanceof ExecFailedError) {
switch (e.kind) {
case "not_found":
console.error("Binary not on PATH:", e.message);
break;
case "permission_denied":
console.error("Not executable (chmod +x?):", e.message);
break;
default:
console.error(`Spawn failed (${e.kind}):`, e.message);
}
// e.errno, e.errnoName, e.stage are also available
} else {
throw e;
}
}
from microsandbox import ExecFailedError
try:
output = await sb.exec("nonexistent")
# program ran, check output.success / output.exit_code
except ExecFailedError as e:
if e.kind == "not_found":
print(f"Binary not on PATH: {e.message}")
elif e.kind == "permission_denied":
print(f"Not executable (chmod +x?): {e.message}")
else:
print(f"Spawn failed ({e.kind}): {e.message}")
# e.errno, e.errno_name, e.stage are also available
// Streaming exec surfaces spawn-failure detail via ExecEventFailed.
h, err := sb.ExecStream(ctx, "nonexistent", nil)
if err != nil {
return err
}
defer h.Close()
for {
ev, err := h.Recv(ctx)
if err != nil {
return err
}
switch ev.Kind {
case m.ExecEventExited:
// Program ran; inspect ev.ExitCode.
case m.ExecEventFailed:
f := ev.Failure // *m.ExecFailure
switch f.Kind {
case "not_found":
log.Printf("Binary not on PATH: %s", f.Message)
case "permission_denied":
log.Printf("Not executable (chmod +x?): %s", f.Message)
default:
log.Printf("Spawn failed (%s): %s", f.Kind, f.Message)
}
// f.Errno (*int), f.ErrnoName, f.Path are also available.
case m.ExecEventDone:
return nil
}
}
The CLI maps these kinds to POSIX-style exit codes: 127 for NotFound, 126 for PermissionDenied and NotExecutable, 1 otherwise. SDK callers reading the error directly don't need to think about exit codes; branch on kind instead.
Creating a sandbox with a name that's already in use (and without replace) surfaces a typed error you can branch on to decide whether to recover (resume the existing one, regenerate the name, etc.).
match Sandbox::builder("worker").image("alpine").create().await { Ok(sb) => { /* ... */ } Err(Error::SandboxAlreadyExists(name)) => { eprintln!("sandbox {name} already exists; resume or pass .replace()"); } Err(e) => return Err(e), }
```typescript TypeScript
import { SandboxAlreadyExistsError } from "microsandbox";
try {
const sb = await Sandbox.builder("worker").image("alpine").create();
} catch (e) {
if (e instanceof SandboxAlreadyExistsError) {
console.error("sandbox already exists; resume or pass replace()");
} else {
throw e;
}
}
from microsandbox import SandboxAlreadyExistsError
try:
sb = await Sandbox.create("worker", image="alpine")
except SandboxAlreadyExistsError:
print("sandbox already exists; resume or pass replace=True")
sb, err := m.CreateSandbox(ctx, "worker",
m.WithImage("alpine"))
if m.IsKind(err, m.ErrSandboxAlreadyExists) {
log.Println("sandbox already exists; resume or pass WithReplace()")
}
Pass replace() / replace=True / --replace / WithReplace() to stop the existing sandbox and recreate it. See Naming conflicts for the grace-period knob.
When a sandbox process exits before the agent relay is ready (mount errors, missing rootfs, network setup failures), the SDK surfaces a typed BootStart / BootStartError. The payload carries the failure stage and errno so callers can recover or report cleanly.
match Sandbox::builder("svc").image("alpine").create().await { Ok(sb) => { /* ... */ } Err(Error::BootStart { name, err }) => { eprintln!("Sandbox {name:?} failed at stage {:?}: {}", err.stage, err.message); if matches!(err.stage, BootErrorStage::Mount) { eprintln!("Hint: a host volume path may not exist."); } } Err(e) => return Err(e), }
```typescript TypeScript
import { BootStartError } from "microsandbox";
try {
const sb = await Sandbox.builder("svc").image("alpine").create();
} catch (e) {
if (e instanceof BootStartError) {
console.error(`Sandbox "${e.name}" failed at stage ${e.stage}: ${e.message}`);
if (e.stage === "mount") {
console.error("Hint: a host volume path may not exist.");
}
} else {
throw e;
}
}
from microsandbox import BootStartError
try:
sb = await Sandbox.create("svc", image="alpine")
except BootStartError as e:
print(f"Sandbox {e.name!r} failed at stage {e.stage}: {e.message}")
if e.stage == "mount":
print("Hint: a host volume path may not exist.")
// The Go SDK surfaces boot failures as *m.Error.
// Inspect Kind and Message for the failure details.
_, err := m.CreateSandbox(ctx, "svc", m.WithImage("alpine"))
var me *m.Error
if errors.As(err, &me) {
log.Printf("sandbox boot failed (kind=%s): %s", me.Kind, me.Message)
}
The CLI prepends the same payload as a styled error: block before any captured log output, so you see "what went wrong + a hint" inline. SDK callers get the structured payload to make their own decisions.
<Tooltip tip="Rust drop and TypeScript await using do not stop microsandbox cloud sandboxes, because cloud handles do not own the host process; call stop() or remove() explicitly."><span className="msb-badge-limited">Limited on cloud <Icon icon="circle-info" size={11} /></span></Tooltip>
Sandboxes hold compute resources, so release them when done. In Rust, Drop handles cleanup when the sandbox goes out of scope. In TypeScript, prefer await using (Node 22+) which calls Sandbox.stop() automatically when the binding leaves scope. In Go, pair every CreateSandbox with a defer that calls Stop + Close.
// Sandbox implements Drop, so resources are released when sb goes out of scope.
// For explicit control, call stop() or kill().
{
let sb = Sandbox::builder("temp")
.image("python")
.create()
.await?;
let output = sb.exec("python", ["-c", "print('hello')"]).await?;
} // sb is dropped here, resources are cleaned up
```typescript TypeScript
async function runTemporary(): Promise<string> {
// `await using` calls Sandbox.stop() when the binding leaves scope.
await using sb = await Sandbox.builder("temp")
.image("python")
.replace()
.create();
const out = await sb.exec("python", ["-c", "print('hello')"]);
return out.stdout();
}
# Use async context manager: auto-kills and removes on exit.
async with await Sandbox.create("temp", image="python") as sb:
output = await sb.exec("python", ["-c", "print('hello')"])
print(output.stdout_text)
sb, err := m.CreateSandbox(ctx, "temp",
m.WithImage("python"),
m.WithReplace(),
)
if err != nil {
log.Fatal(err)
}
defer func() {
stopCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
_, _ = sb.Stop(stopCtx)
_ = sb.Close()
}()
out, _ := sb.Exec(ctx, "python", []string{"-c", "print('hello')"})
fmt.Println(out.Stdout())