Error Handling

One Machine class, two backends. These are the typed exceptions raised by the smolmachines SDK (Node) / smol module (Python), on either the local embedded engine (default) or the smolfleet cloud. They wrap the underlying engine/transport errors into a small typed hierarchy. The standalone hosted Cloud API returns HTTP status codes (including 402, 422, 429, 503) directly.

The SDK exposes four error classes, all subclasses of SmolError.

Error Types

ErrorDescription
SmolErrorBase class for all SDK errors. Carries a machine-readable code.
NotSupportedErrorThe active backend can’t serve this operation (e.g. a local-only call on cloud, or vice versa). code = "NOT_SUPPORTED".
InvalidConfigErrorA required configuration value is missing or invalid (a usage error). code = "INVALID_CONFIG".
ExecutionErrorA command ran but exited non-zero. Raised by assertSuccess() / assert_success(). code = "COMMAND_FAILED".

Any error coming out of the native engine is normalized into a SmolError whose code is the engine’s bracketed code (e.g. KVM_UNAVAILABLE), so you can always branch on err.code or instanceof / isinstance.

Basic Error Handling

import {
  Machine,
  SmolError,
  NotSupportedError,
  InvalidConfigError,
  ExecutionError,
} from 'smolmachines';

try {
  const machine = await Machine.create({ name: 'test' });
  const result = await machine.exec(['some-command']);
  result.assertSuccess();
} catch (error) {
  if (error instanceof ExecutionError) {
    console.log(`Command failed with exit code: ${error.exitCode}`);
    console.log(`stderr: ${error.stderr}`);
  } else if (error instanceof InvalidConfigError) {
    console.log(`Invalid configuration: ${error.message}`);
  } else if (error instanceof NotSupportedError) {
    console.log(`Not supported on this backend: ${error.message}`);
  } else if (error instanceof SmolError) {
    console.log(`SDK error [${error.code}]: ${error.message}`);
  } else {
    throw error;
  }
}
from smol import (
    Machine,
    MachineConfig,
    SmolError,
    NotSupportedError,
    InvalidConfigError,
    ExecutionError,
)

try:
    machine = Machine.create(MachineConfig(name="test"))
    result = machine.exec(["some-command"])
    result.assert_success()
except ExecutionError as e:
    print(f"Command failed with exit code: {e.exit_code}")
    print(f"stderr: {e.stderr}")
except InvalidConfigError as e:
    print(f"Invalid configuration: {e}")
except NotSupportedError as e:
    print(f"Not supported on this backend: {e}")
except SmolError as e:
    print(f"SDK error [{e.code}]: {e}")

SmolError

Every SDK error carries a machine-readable code. Wrapping arbitrary engine errors is done by wrap_native_error (Python), which parses the engine’s [CODE] message format into a SmolError.

class SmolError extends Error {
  readonly code: string;  // e.g. "NOT_SUPPORTED", "KVM_UNAVAILABLE"
}
class SmolError(Exception):
    code: str       # e.g. "NOT_SUPPORTED", "KVM_UNAVAILABLE"
    message: str

ExecutionError

Raised when a command exits with a non-zero status — via assertSuccess() / assert_success().

class ExecutionError extends SmolError {
  readonly exitCode: number;
  readonly stdout: string;
  readonly stderr: string;
}

try {
  const result = await machine.exec(['false']);  // exits with 1
  result.assertSuccess();  // throws ExecutionError
} catch (error) {
  if (error instanceof ExecutionError) {
    console.log(`Exit code: ${error.exitCode}`);
    console.log(`Output: ${error.stdout}`);
    console.log(`Error: ${error.stderr}`);
  }
}
class ExecutionError(SmolError):
    command: list[str] | str
    exit_code: int
    stdout: str
    stderr: str

try:
    result = machine.exec(["false"])  # exits with 1
    result.assert_success()  # raises ExecutionError
except ExecutionError as e:
    print(f"Exit code: {e.exit_code}")
    print(f"Output: {e.stdout}")
    print(f"Error: {e.stderr}")

Handling Without Exceptions

ExecResult lets you check the outcome without throwing:

const result = await machine.exec(['maybe-fails']);

if (!result.success) {
  console.log(`Command failed with code ${result.exitCode}`);
  console.log(`stderr: ${result.stderr}`);
} else {
  console.log(`Success: ${result.stdout}`);
}
result = machine.exec(["maybe-fails"])

if not result.success:
    print(f"Command failed with code {result.exit_code}")
    print(f"stderr: {result.stderr}")
else:
    print(f"Success: {result.stdout}")

Cleanup on Error

Always clean up the machine, even on error:

// try/finally — there is no wrapper helper
const machine = await Machine.create({ name: 'cleanup-example' });
try {
  await machine.exec(['risky-command']);
} finally {
  await machine.delete();
}
# Context manager (recommended) — deletes on exit, even on error
with Machine.create(MachineConfig(name="cleanup-example")) as machine:
    machine.exec(["risky-command"])

# Or manually with try/finally
machine = Machine.create(MachineConfig(name="cleanup-example"))
try:
    machine.exec(["risky-command"])
finally:
    machine.delete()