n8n-io/n8n · critical · ConnectionLostError

Browser connection lost: ${reason}

Error message

Browser connection lost: ${reason}

What it means

ConnectionLostError(this.disconnectReason) is thrown by getConnection() when this.state is null AND this.disconnectReason is set — i.e. the connection was previously live and dropped unexpectedly (the onDisconnect handler recorded the reason). This distinguishes an involuntary disconnect from a never-connected state.

Source

Thrown at packages/@n8n/mcp-browser/src/connection.ts:168

		if (pending) await pending.close().catch(() => {});

		if (!this.state) return; // already disconnected — idempotent

		const { adapter } = this.state;
		this.state = null;
		this.disconnectReason = undefined;

		try {
			await adapter.close();
		} catch {
			// Browser may already be dead — that's fine
		}
	}

	getConnection(): ConnectionState {
		if (!this.state) {
			if (this.disconnectReason) {
				throw new ConnectionLostError(this.disconnectReason);
			}
			throw new NotConnectedError();
		}
		return this.state;
	}

	get isConnected(): boolean {
		return this.state !== null;
	}

	async shutdown(): Promise<void> {
		await this.disconnect();
	}

	getResolvedConfig(): ResolvedConfig {
		return this.config;
	}

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Call browser_disconnect to clear disconnectReason, then browser_connect to re-establish.
  2. Subscribe to disconnect events and proactively reconnect before issuing further tools.
  3. Inspect the reason (error.reason) to decide whether a retry is worthwhile (e.g. heartbeat_timeout may be transient).

Example fix

// before — tool call after silent drop
const conn = connection.getConnection(); // throws ConnectionLostError

// after — reconnect on detected drop
try { const conn = connection.getConnection(); }
catch (e) { if (e instanceof ConnectionLostError) { await connection.disconnect(); await connection.connect(); } }
Defensive patterns

Strategy: try-catch

Validate before calling

if (!connection.isConnected) {
  await connection.disconnect(); // clears disconnectReason
  await connection.connect();
}

Type guard

function hasDisconnectReason(c: BrowserConnection): boolean {
  // disconnectReason is private; infer via isConnected + prior drop flag
  return !c.isConnected && /* tracked externally */ dropped;
}

Try / catch

try {
  const conn = connection.getConnection();
} catch (e) {
  if (e instanceof ConnectionLostError) {
    await connection.disconnect();
    await connection.connect();
  } else throw e;
}

Prevention

When it happens

Trigger: Calling any tool that internally calls getConnection() after the adapter fired onDisconnect with a reason (browser_closed, extension_disconnected, debugger_detached, network_error, or heartbeat_timeout). The state was nulled by the disconnect handler but disconnect() was not explicitly called.

Common situations: User closed the browser mid-session. Extension service worker evicted. Debugger banner dismissed in Chrome. Any async tool firing after the disconnect event but before the agent calls browser_disconnect/browser_connect.

Related errors


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