mastra-ai/mastra · error

Cannot start a destroyed sandbox

Error message

Cannot start a destroyed sandbox

What it means

MastraSandbox._start() throws if the sandbox's lifecycle status is 'destroyed'. A destroyed sandbox has released its resources permanently and cannot be restarted; starting over it would run on top of broken state. The method also awaits any in-flight stop/destroy promises first so a concurrent destroy is honored before the check.

Source

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

   * Subclasses override `start()` to provide their startup logic.
   */
  async _start(): Promise<SandboxStartResult | void> {
    // Already running — definitionally not a fresh create. Reporting
    // 'connected' (rather than nothing) keeps every path through the wrapper
    // result-bearing for providers whose `start()` always reports one.
    if (this.status === 'running') {
      return { outcome: 'connected' };
    }

    // Wait for in-flight stop/destroy before starting.
    // Intentionally no .catch() — if teardown is failing, _start() should propagate
    // that error rather than silently starting on top of a broken state.
    if (this._stopPromise) await this._stopPromise;
    if (this._destroyPromise) await this._destroyPromise;

    // Cannot start a destroyed sandbox
    if (this.status === 'destroyed') {
      throw new Error('Cannot start a destroyed sandbox');
    }

    // Start already in progress — join it and share its result. The slot is
    // cleared on settle, so a failed attempt is never latched.
    if (this._startPromise) {
      return this._startPromise;
    }

    // Create and store the start promise
    this._startPromise = this._executeStart();

    try {
      return await this._startPromise;
    } finally {
      this._startPromise = undefined;
    }
  }

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Create a new sandbox instance instead of restarting the destroyed one
  2. Check `sandbox.status !== 'destroyed'` before calling start(), and recreate when it is destroyed
  3. Fix ownership: ensure only one code path can destroy the sandbox and refresh references after destroy

Example fix

// before
await sandbox.destroy();
await sandbox.start(); // throws
// after
await sandbox.destroy();
sandbox = new LocalSandbox(options);
await sandbox.start();
Defensive patterns

Strategy: validation

Validate before calling

if (sandbox.status === 'destroyed') {
  sandbox = createSandbox(options);
}
await sandbox.start();

Type guard

function isRestartable(s: { status: string }): s is { status: Exclude<string, 'destroyed'> } {
  return s.status !== 'destroyed';
}

Try / catch

try {
  await sandbox.start();
} catch (err) {
  if (/Cannot start a destroyed sandbox/.test(String(err?.message))) {
    sandbox = new LocalSandbox(options);
    await sandbox.start();
  } else throw err;
}

Prevention

When it happens

Trigger: Calling `start()`/`ensureRunning()` (directly or via internal helpers) after `destroy()` or `dispose()` has completed on the same sandbox instance; holding a stale reference to a sandbox that was destroyed elsewhere.

Common situations: Long-lived caches or DI containers holding sandbox instances across lifecycles; retry logic that calls start() on a handle whose destroy already ran; hot-reload in dev servers reusing old sandbox objects.

Related errors


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