n8n-io/n8n · error · BrowserExecutableNotFoundError

No executable path for

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.

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.

Appendix: 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)