{"record":{"id":"d2f761f42d20cb06","repo":"n8n-io/n8n","slug":"browser-connection-lost-reason","errorCode":null,"errorMessage":"Browser connection lost: ${reason}","messagePattern":"Browser connection lost: (.+?)","errorType":"exception","errorClass":"ConnectionLostError","httpStatus":null,"severity":"critical","filePath":"packages/@n8n/mcp-browser/src/connection.ts","lineNumber":168,"sourceCode":"\t\tif (pending) await pending.close().catch(() => {});\n\n\t\tif (!this.state) return; // already disconnected — idempotent\n\n\t\tconst { adapter } = this.state;\n\t\tthis.state = null;\n\t\tthis.disconnectReason = undefined;\n\n\t\ttry {\n\t\t\tawait adapter.close();\n\t\t} catch {\n\t\t\t// Browser may already be dead — that's fine\n\t\t}\n\t}\n\n\tgetConnection(): ConnectionState {\n\t\tif (!this.state) {\n\t\t\tif (this.disconnectReason) {\n\t\t\t\tthrow new ConnectionLostError(this.disconnectReason);\n\t\t\t}\n\t\t\tthrow new NotConnectedError();\n\t\t}\n\t\treturn this.state;\n\t}\n\n\tget isConnected(): boolean {\n\t\treturn this.state !== null;\n\t}\n\n\tasync shutdown(): Promise<void> {\n\t\tawait this.disconnect();\n\t}\n\n\tgetResolvedConfig(): ResolvedConfig {\n\t\treturn this.config;\n\t}\n","sourceCodeStart":150,"sourceCodeEnd":186,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/@n8n/mcp-browser/src/connection.ts#L150-L186","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Call browser_disconnect to clear disconnectReason, then browser_connect to re-establish.","Subscribe to disconnect events and proactively reconnect before issuing further tools.","Inspect the reason (error.reason) to decide whether a retry is worthwhile (e.g. heartbeat_timeout may be transient)."],"exampleFix":"// before — tool call after silent drop\nconst conn = connection.getConnection(); // throws ConnectionLostError\n\n// after — reconnect on detected drop\ntry { const conn = connection.getConnection(); }\ncatch (e) { if (e instanceof ConnectionLostError) { await connection.disconnect(); await connection.connect(); } }","handlingStrategy":"try-catch","validationCode":"if (!connection.isConnected) {\n  await connection.disconnect(); // clears disconnectReason\n  await connection.connect();\n}","typeGuard":"function hasDisconnectReason(c: BrowserConnection): boolean {\n  // disconnectReason is private; infer via isConnected + prior drop flag\n  return !c.isConnected && /* tracked externally */ dropped;\n}","tryCatchPattern":"try {\n  const conn = connection.getConnection();\n} catch (e) {\n  if (e instanceof ConnectionLostError) {\n    await connection.disconnect();\n    await connection.connect();\n  } else throw e;\n}","preventionTips":["Subscribe to onDisconnect and proactively reconnect.","Clear agent-side connection assumptions on any disconnect event.","Inspect error.reason to decide retry vs. surface-to-user."],"tags":["connection-lost","disconnect-reason","reconnect","state"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}