n8n-io/n8n · error · BrowserExecutableNotFoundError

No executable path for ${browser}

Error message

No executable path for ${browser}

What it means

Thrown as BrowserExecutableNotFoundError by the Playwright adapter's launch() (local mode) when resolvedConfig.browsers.get(config.browser) returns an entry with no executablePath (or returns undefined). Identical semantics to the agent-browser variant: the browser was either not detected or detected without a resolvable binary. The Playwright adapter then needs the path to execFile the browser with the extension connect URL.

Source

Thrown at packages/@n8n/mcp-browser/src/adapters/playwright.ts:140

		// Local mode — connect to the user's running Chrome via extension bridge.
		// The CDPRelayServer bridges Playwright ↔ Chrome extension (chrome.debugger).
		this.relay = new CDPRelayServer();
		const port = await this.relay.listen();
		const extensionEndpoint = this.relay.extensionEndpoint(port);

		// Open the extension's connect page with the relay URL so it auto-connects.
		// `N8N_EVAL_AUTO_BROWSER_CONNECT=1` (set by the eval daemon spawn) appends
		// `autoConnect=1` so the extension UI clicks Connect itself — keeps eval
		// runs human-out-of-the-loop across reconnect cycles. The extension only
		// honors `autoConnect=1` when the relay URL is localhost (which the relay
		// always is — see `cdp-relay.ts`), so a crafted chrome-extension URL
		// pointing at a remote relay can't trigger this path.
		const autoConnect = process.env.N8N_EVAL_AUTO_BROWSER_CONNECT === '1';
		const connectUrl = buildExtensionConnectUrl(extensionEndpoint, { autoConnect });
		const browserInfo = this.resolvedConfig.browsers.get(config.browser);
		const chromePath = browserInfo?.executablePath;
		if (!chromePath) {
			throw new BrowserExecutableNotFoundError(config.browser);
		}

		log.debug('launching browser:', chromePath);
		log.debug('connect URL:', connectUrl);

		// Launch the browser and detect early spawn failures (ENOENT, EACCES, etc.)
		await new Promise<void>((resolve, reject) => {
			const child = execFile(chromePath, [connectUrl]);
			const earlyFailTimer = setTimeout(() => resolve(), 2_000);
			child.on('error', (spawnError: Error) => {
				clearTimeout(earlyFailTimer);
				log.error('browser spawn error:', spawnError.message);
				reject(new BrowserExecutableNotFoundError(`${config.browser} (${spawnError.message})`));
			});
		});

		// Wait for the extension to connect and attach to tabs
		log.debug('waiting for extension...');

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Provide an explicit executablePath in the browser config for the selected browser.
  2. Re-run browser detection or rebuild the config after installing Chrome/Chromium.
  3. Verify the binary is executable by the gateway process user (chmod +x, check ownership).
  4. For headless/Docker, install chromium and point executablePath at it.

Example fix

// before
await playwrightAdapter.launch({ browser: 'chrome' });

// after — explicit path
await playwrightAdapter.launch({
  browser: 'chrome',
  executablePath: process.env.CHROME_PATH ?? '/usr/bin/chromium',
});
Defensive patterns

Strategy: validation

Validate before calling

function hasBrowserExecutable(config: ResolvedConfig, browser: string): boolean {
  return Boolean(config.browsers.get(browser)?.executablePath);
}

Type guard

function isBrowserExecutableAvailable(config: ResolvedConfig, browser: string): boolean {
  return Boolean(config.browsers.get(browser)?.executablePath);
}

Prevention

When it happens

Trigger: Calling the Playwright adapter's launch(config) in local mode (extension-bridge mode, not remote/connect mode) where the selected browser's executablePath is missing. Also thrown a second way at line 153 when execFile(chromePath) emits an 'error' event (spawn failure like ENOENT/EACCES) within the 2-second early-fail window — that variant appends the spawn error message to the browser name.

Common situations: Chrome/Chromium not installed where the detector looks (non-standard path on Linux, app-not-installed on macOS); browser uninstalled after config was built; running in Docker/headless without a Chromium; the detected path points to a broken symlink; EACCES because the gateway process lacks execute permission on the binary.

Related errors


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