n8n-io/n8n · error
Sandbox "${this.name}" failed to start (status: ${this.statu
Error message
Sandbox "${this.name}" failed to start (status: ${this.status}) What it means
The post-start invariant in ensureRunning(): after awaiting raceWithAbort(_start), status must be 'running'. If it isn't, _start() returned without flipping status to 'running' (subclass bug), aborted, or landed in an error/pending state. The throw surfaces that the start silently no-op'd rather than letting executeCommand proceed against an unstarted sandbox.
Source
Thrown at packages/@n8n/agents/src/workspace/sandbox/base-sandbox.ts:138
this.status = 'pending';
}
async ensureRunning(options?: AbortableOptions): Promise<void> {
if (this.status === 'destroyed') {
throw new Error(`Sandbox "${this.name}" has been destroyed`);
}
if (this.status === 'destroying') {
if (this.destroyPromise) await this.destroyPromise.catch(() => {});
throw new Error(`Sandbox "${this.name}" has been destroyed`);
}
if (this.status === 'stopping') {
if (this.stopPromise) await this.stopPromise.catch(() => {});
}
if (this.status !== 'running') {
await raceWithAbort(async () => await this._start(), options?.abortSignal);
}
if (this.status !== 'running') {
throw new Error(`Sandbox "${this.name}" failed to start (status: ${this.status})`);
}
}
async executeCommand(
command: string,
args?: string[],
options?: ExecuteCommandOptions,
): Promise<CommandResult> {
await this.ensureRunning({ abortSignal: options?.abortSignal });
if (!this.processes) {
throw new Error(`Sandbox "${this.name}" has no process manager`);
}
const fullCommand = args?.length ? `${command} ${args.map(shellQuote).join(' ')}` : command;
const handle = await this.processes.spawn(fullCommand, options);
return await raceWithAbort(
async () =>
await handle.wait({
onStdout: options?.onStdout,View on GitHub (pinned to 5ac6606e81)
Solutions
- Ensure subclass executeStart() sets `this.status = 'ready'`/`'running'` only after the sandbox is truly ready, and throws on failure (so status stays non-running and this error correctly fires).
- If aborted, check the AbortSignal — don't reuse the aborted sandbox; recreate and retry without the abort.
- Inspect the status in the message to distinguish 'pending' (never started) from 'error'.
Example fix
// before
async executeStart() {
await this.provider.create();
// forgot to set status
}
// after
async executeStart() {
await this.provider.create();
this.status = 'running';
} Defensive patterns
Strategy: try-catch
Try / catch
try {
await sandbox.ensureRunning();
} catch (e) {
if (e instanceof Error && /failed to start/.test(e.message)) {
// inspect sandbox.status: 'pending'/'error' → recreate; aborted → retry without the abort
} else throw e;
} Prevention
- In subclass executeStart(), set status='running' only after true readiness and rethrow on failure.
- Don't pass an already-aborted AbortSignal to ensureRunning().
- Log status at the end of executeStart to catch silent no-ops.
When it happens
Trigger: A subclass executeStart() resolved without setting status='running'; start was aborted mid-flight (raceWithAbort propagated abort, _start exited); start partially succeeded then hit an error path that left status as 'pending'/'error'.
Common situations: Custom BaseSandbox subclass whose executeStart() returns early on a recoverable condition without updating status; Daytona/E2B start aborted by an AbortSignal; provider returned success but the readiness probe was skipped.
Related errors
- Sandbox "${this.name}" has been destroyed
- Filesystem "${this.id}" is not ready (status: ${this.status}
- Daytona sandbox "${this.id}" is not running
- Workspace has no sandbox
- Sandbox does not support command execution
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/8b7fc8ac345be597.
Report an issue: GitHub.