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
- Create a new sandbox instance instead of restarting the destroyed one
- Check `sandbox.status !== 'destroyed'` before calling start(), and recreate when it is destroyed
- 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
- Null out or replace references after destroy() so stale handles can't be reused
- Centralize sandbox lifecycle (create/start/destroy) in one owner module
- In retry loops, recreate the sandbox rather than re-calling start() on the same instance
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
- Sandbox provider "${config.sandbox.provider}" does not suppo
- No active thread on this session
- No source thread to clone
- Mode not found: ${this.#id}
- SandboxNotReadyError: sandbox with id '${this.id}' is not re
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/7d1bc7314694b0ba.
Report an issue: GitHub.