docs/sdk/errors.mdx
TypeScript, Rust, Python, and Go surface typed errors so you can match specific failure modes instead of parsing strings. TypeScript exposes a dedicated subclass per variant (use instanceof), Rust has an Error enum, Python provides dedicated exception classes, and Go provides an *Error value with an ErrorKind discriminator matched via m.IsKind(err, kind) or errors.As. Ruby currently exposes Microsandbox::Error; where no dedicated subclass exists, its reference documents the stable message contract explicitly.
const sb = await Sandbox.builder("worker") .image("python") .connectOrCreate();
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;
}
}
```rust Rust
use microsandbox::{Sandbox, Error};
let sb = Sandbox::builder("worker")
.image("python")
.connect_or_create()
.await?;
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),
}
from microsandbox import ExecTimeoutError, Sandbox
sb = await Sandbox.connect_or_create("worker", 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"
)
sb, err := m.ConnectOrCreateSandbox(ctx, "worker", m.WithImage("python"))
if err != nil {
log.Fatal(err)
}
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)
}
}
require "microsandbox"
sb = Microsandbox::Sandbox.connect_or_create("worker", image: "python")
begin
output = sb.exec("python", ["script.py"])
warn "Exit #{output.exit_code}: #{output.stderr}" unless output.success?
rescue Microsandbox::Error => error
warn error.message
end
exec() distinguishes between:
ExecOutput with a non-zero code. This is not an error in the SDK sense; it's a normal result.Rust exposes a structured ExecFailed payload, and Go exposes the same detail on streaming execution events. Common failure kinds include NotFound (binary missing on PATH), PermissionDenied, NotExecutable, BadCwd, BadArgs, ResourceLimit, UserSetupFailed, OutOfMemory, PtySetupFailed, and Other.
use microsandbox::{protocol::exec::ExecFailureKind, Error};
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),
}
from microsandbox import MicrosandboxError
try:
output = await sb.exec("nonexistent")
# program ran, check output.success / output.exit_code
except MicrosandboxError as e:
print(f"Program could not be started: {e}")
require "microsandbox"
begin
output = sandbox.exec("nonexistent")
# program ran, check output.success / output.exit_code
rescue Microsandbox::Error => e
warn "Program could not be started: #{e.message}"
end
// 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, and 1 otherwise.
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.).
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; } }
```rust Rust
use microsandbox::{Error, Sandbox};
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),
}
from microsandbox import Sandbox, 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()")
}
begin
sb = Microsandbox::Sandbox.create("worker", image: "alpine")
rescue Microsandbox::Error => error
raise unless error.message.include?("already exists")
warn "sandbox already exists; use connect_or_create to reuse it or replace: true to recreate it"
end
Use connect_or_create and its language-specific equivalents to reuse the existing sandbox without changing its configuration. Pass replace() / replace=True / replace: true / --replace / WithReplace() only when you intend to stop the existing sandbox and create a new one. See Naming conflicts for the grace-period setting.
A Sandbox or SandboxHandle keeps the ID of the exact sandbox it represents. If that sandbox is removed and the name is reused, lifecycle methods refuse to act on the replacement and return a typed error.
try { await staleHandle.destroy(); } catch (error) { if (!(error instanceof SandboxReplacedError)) throw error; }
```rust Rust
match stale_handle.destroy().await {
Err(Error::SandboxReplaced { expected, actual, .. }) => {
eprintln!("refusing stale operation: {expected} -> {actual}");
}
result => result?,
}
from microsandbox import SandboxReplacedError
try:
await stale_handle.destroy()
except SandboxReplacedError:
pass
if err := staleHandle.Destroy(ctx); m.IsKind(err, m.ErrSandboxReplaced) {
log.Println("refusing stale lifecycle operation")
}
begin
stale_handle.destroy
rescue Microsandbox::Error => error
raise unless error.message.include?("was replaced")
end
Ruby does not yet expose a dedicated stale-identity subclass. Until it does, Microsandbox::Error with the stable was replaced message is the Ruby-specific contract; the operation still refuses to act on the replacement.
When a sandbox process exits before the agent relay is ready, creation returns or raises an SDK error. Rust exposes a structured BootStart payload with the failure stage and underlying message.
try { const sb = await Sandbox.builder("svc").image("alpine").create(); } catch (e) { console.error("Sandbox failed to start:", e); }
```rust Rust
use microsandbox::{Error, Sandbox};
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);
}
Err(e) => return Err(e),
}
from microsandbox import MicrosandboxError, Sandbox
try:
sb = await Sandbox.create("svc", image="alpine")
except MicrosandboxError as e:
print(f"Sandbox failed to start: {e}")
_, err := m.CreateSandbox(ctx, "svc", m.WithImage("alpine"))
if err != nil {
log.Printf("sandbox failed to start: %v", err)
}
require "microsandbox"
begin
sandbox = Microsandbox::Sandbox.create("svc", image: "alpine")
rescue Microsandbox::Error => e
warn "Sandbox failed to start: #{e.message}"
end
The CLI prints the startup failure before any captured log output so the immediate cause stays visible.
<Tooltip tip="TypeScript await using and Rust drop 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 TypeScript, prefer await using (Node 22+) which calls Sandbox.stop() automatically when the binding leaves scope. In Rust, Drop handles cleanup when the sandbox goes out of scope. In Go, pair every CreateSandbox with a defer that calls Stop + Close.
const out = await sb.exec("python", ["-c", "print('hello')"]);
return out.stdout();
}
```rust Rust
use microsandbox::Sandbox;
// 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
# 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())
Microsandbox::Sandbox.with("temp", image: "python", replace: true) do |sandbox|
output = sandbox.exec("python", ["-c", "print('hello')"])
puts output.stdout
end