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

  1. 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).
  2. If aborted, check the AbortSignal — don't reuse the aborted sandbox; recreate and retry without the abort.
  3. 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

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


AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12). Data as JSON: /api/errors/8b7fc8ac345be597. Report an issue: GitHub.