n8n-io/n8n · error · Error

Invalid sandbox provider "${String(exhaustiveProvider)}". Se

Error message

Invalid sandbox provider "${String(exhaustiveProvider)}". Set N8N_INSTANCE_AI_SANDBOX_PROVIDER to one of: ${VALID_PROVIDERS.join(', ')}.

What it means

This is a compile-time exhaustiveness guard. The line `const exhaustiveProvider: never = provider` only compiles when every SandboxProvider union member has been handled in an earlier branch. If a new provider is added to the SandboxProvider type without a matching branch, the `never` assignment fails to compile; if it somehow reaches runtime (e.g. via unsafe casts defeating narrowing), this throw fires as a defensive fallback.

Source

Thrown at packages/@n8n/instance-ai/evaluations/harness/sandbox-config.ts:84

	if (provider === 'n8n-sandbox') {
		const serviceUrl = env.N8N_SANDBOX_SERVICE_URL;
		if (!serviceUrl) {
			throw new Error(
				'N8N_SANDBOX_SERVICE_URL is required for sandbox provider "n8n-sandbox". Set it to the service URL.',
			);
		}
		const apiKey = env.N8N_SANDBOX_SERVICE_API_KEY;
		return {
			enabled: true,
			provider: 'n8n-sandbox',
			serviceUrl,
			...(apiKey ? { apiKey } : {}),
			timeout,
		};
	}

	const exhaustiveProvider: never = provider;
	throw new Error(
		`Invalid sandbox provider "${String(exhaustiveProvider)}". Set N8N_INSTANCE_AI_SANDBOX_PROVIDER to one of: ${VALID_PROVIDERS.join(', ')}.`,
	);
}

function parseTimeout(raw: string | undefined): number | undefined {
	if (raw === undefined || raw === '') return undefined;
	const n = Number(raw);
	if (!Number.isFinite(n) || n <= 0) {
		throw new Error(
			`N8N_INSTANCE_AI_SANDBOX_TIMEOUT must be a positive number of ms, got "${raw}".`,
		);
	}
	return n;
}

function parsePositiveInt(raw: string | undefined, varName: string): number | undefined {
	if (raw === undefined || raw === '') return undefined;
	const n = Number(raw);

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Add a dedicated `if (provider === '<new-provider>')` branch returning the right SandboxConfig shape.
  2. Add the new provider to VALID_PROVIDERS so the upfront guard accepts it.
  3. Remove any `as SandboxProvider` casts in callers that smuggle in unrecognised values.

Example fix

// before — new provider 'k8s' added to the union, no branch
// const exhaustiveProvider: never = provider; // compile error

// after — add a branch + VALID_PROVIDERS entry
const VALID_PROVIDERS: SandboxProvider[] = ['n8n-sandbox', 'daytona', 'k8s'];
// ...
if (provider === 'k8s') { return { enabled: true, provider: 'k8s', /* ... */ }; }
Defensive patterns

Strategy: type-guard

Validate before calling

import type { SandboxProvider } from '../../src/workspace/create-workspace';

function assertAllProvidersHandled(p: SandboxProvider): never {
  // If this compiles, every union member is handled upstream.
  throw new Error(`unhandled provider ${p}`);
}

// At the call site, ensure the switch/if-chain covers every member so the
// `never` assignment compiles; add a unit test per provider branch.

Type guard

function isKnownSandboxProvider(v: unknown): v is 'n8n-sandbox' | 'daytona' {
  return v === 'n8n-sandbox' || v === 'daytona';
}

Try / catch

try {
  resolveSandboxConfig(process.env);
} catch (e) {
  if (e instanceof Error && /Invalid sandbox provider/.test(e.message)) {
    // a new provider was added to the union without a branch — add one
  }
  throw e;
}

Prevention

When it happens

Trigger: A new SandboxProvider literal was added to the union but no `if (provider === ...)` branch was added; the provider value was injected via an `as` cast that bypassed the VALID_PROVIDERS check at the top of the function.

Common situations: Extending the SandboxProvider type during feature work and forgetting to handle the new case here; a test casting arbitrary strings to SandboxProvider.

Related errors


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