n8n-io/n8n · critical · ConnectionLostError

Browser connection lost: The connection to the browser exten

Error message

Browser connection lost: The connection to the browser extension was lost

What it means

ConnectionLostError('network_error') is thrown at the top of ExtensionConnection.send when the WebSocket readyState is not OPEN. Before queueing any request to the extension, send() checks the socket; a non-OPEN state (CONNECTING, CLOSING, CLOSED) means the channel is unusable. This is the pre-flight guard before any bytes are written.

Source

Thrown at packages/@n8n/mcp-browser/src/cdp-relay.ts:953

		});
		this.ws.on('error', (error) => {
			log.debug('ExtensionConnection WebSocket error:', error);
			this.handleClose();
		});
		this.ws.on('pong', () => {
			this.lastPongAt = Date.now();
		});

		this.startHeartbeat();
	}

	async send<M extends keyof ExtensionCommands>(
		method: M,
		params: ExtensionCommands[M]['params'],
		timeoutMs = 30_000,
	): Promise<unknown> {
		if (this.ws.readyState !== WebSocket.OPEN) {
			throw new ConnectionLostError('network_error');
		}

		const id = ++this.lastId;
		const payload = JSON.stringify({ id, method, params });
		log.debug('→ EXT:', method, 'id=' + String(id));
		try {
			this.ws.send(payload);
		} catch {
			throw new ConnectionLostError('network_error');
		}

		return await new Promise((resolve, reject) => {
			const timer = setTimeout(() => {
				this.callbacks.delete(id);
				log.error(
					'→ EXT TIMEOUT:',
					method,
					'id=' + String(id),

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Wait for the connection open event before issuing commands, or reconnect via browser_connect.
  2. Track connection liveness and queue/retry commands only while OPEN.
  3. Call browser_disconnect then browser_connect to obtain a fresh ExtensionConnection.

Example fix

// before — send before socket open
extConn.send('createTab', { url }); // readyState=CLOSED -> throws

// after — reconnect to get a fresh OPEN socket
await connection.disconnect(); await connection.connect();
await extConn.send('createTab', { url });
Defensive patterns

Strategy: validation

Validate before calling

if (extConn.ws.readyState !== WebSocket.OPEN) {
  await connection.disconnect();
  await connection.connect();
}

Type guard

function socketOpen(ws: WebSocket): boolean {
  return ws.readyState === WebSocket.OPEN;
}

Try / catch

try {
  return await extConn.send(method, params);
} catch (e) {
  if (e instanceof ConnectionLostError && e.reason === 'network_error') {
    await connection.disconnect();
    await connection.connect();
    return await extConn.send(method, params);
  }
  throw e;
}

Prevention

When it happens

Trigger: Issuing any extension command (attachTab, createTab, forwardCDPCommand, etc.) when the WebSocket has transitioned out of OPEN — after handleClose fired, after close() was called, or while the socket is still handshaking.

Common situations: Command issued immediately after construction before the WS finishes opening. Command issued after the OS reported the TCP connection dead. Race between the disconnect event and an in-flight caller.

Related errors


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