mastra-ai/mastra · error · SandboxNotReadyError

SandboxNotReadyError: sandbox with id '${this.id}' is not re

Error message

SandboxNotReadyError: sandbox with id '${this.id}' is not ready

What it means

ensureRunning() throws SandboxNotReadyError when the sandbox has already been destroyed — the instance can no longer execute commands or be restarted. It fails fast instead of attempting operations on a dead sandbox handle.

Source

Thrown at packages/core/src/workspace/sandbox/mastra-sandbox.ts:572

   * where operations should automatically start the sandbox if needed.
   *
   * This is the lazy entry point into the id-keyed getOrCreate contract
   * described on {@link start}.
   *
   * @throws {SandboxNotReadyError} if the sandbox fails to reach 'running' status
   *
   * @example
   * ```typescript
   * async executeCommand(command: string): Promise<CommandResult> {
   *   await this.ensureRunning();
   *   // Now safe to use the sandbox
   * }
   * ```
   */
  async ensureRunning(): Promise<void> {
    // Already destroyed — cannot use this sandbox
    if (this.status === 'destroyed') {
      throw new SandboxNotReadyError(this.id);
    }
    // During teardown the sandbox is still operational (e.g. destroy()
    // may need to list/kill processes).  Allow operations to proceed
    // without trying to restart.
    if (this.status === 'destroying' || this.status === 'stopping') {
      return;
    }
    if (this.status !== 'running') {
      await this._start();
    }
    if (this.status !== 'running') {
      throw new SandboxNotReadyError(this.id);
    }
  }

  /**
   * Stop the sandbox (wrapper with status management and race-condition safety).
   *

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Create a new sandbox instance instead of reusing the destroyed one.
  2. Check sandbox.status before use and recreate when it is 'destroyed'.
  3. Remove duplicate destroy() calls (e.g. both a timeout handler and afterEach) that kill the sandbox while it is still needed.

Example fix

// before
await sandbox.executeCommand('ls');
// after
if (sandbox.status === 'destroyed') {
  sandbox = await createSandbox();
}
await sandbox.executeCommand('ls');
Defensive patterns

Strategy: validation

Validate before calling

if (sandbox.status === 'destroyed') {
  sandbox = await createNewSandbox(); // recreate before use
}

Type guard

function isUsable(sandbox: { status: string }): boolean {
  return sandbox.status !== 'destroyed';
}

Try / catch

try {
  await sandbox.executeCommand(cmd);
} catch (e) {
  if (e.name === 'SandboxNotReadyError' && sandbox.status === 'destroyed') {
    sandbox = await createNewSandbox();
    return sandbox.executeCommand(cmd);
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling executeCommand()/ensureRunning() (or the `sandbox` helper) on a sandbox instance after destroy() completed, e.g. keeping a long-lived reference across a shutdown path or using a cached sandbox from a previous run.

Common situations: App code caching a sandbox in a module-level variable; a worker holding a sandbox reference that was destroyed by a timeout/cleanup handler; tests that destroy in afterEach but still use it in the last assertion.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/1b3e3b1c06189ba0. Report an issue: GitHub.