{"record":{"id":"463a0233c9de7e27","repo":"SeleniumHQ/selenium","slug":"bidi-connection-is-closed","errorCode":null,"errorMessage":"BiDi connection is closed","messagePattern":"BiDi connection is closed","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"javascript/selenium-webdriver/bidi/index.js","lineNumber":205,"sourceCode":"      if (this.connected) {\n        resolve()\n        return\n      }\n      // Park the waiter in a Set so the constructor's 'open' handler can\n      // resolve it and _failPending() can reject it. Avoids attaching socket\n      // listeners that close()'s removeAllListeners('close') would strip.\n      this._connectWaiters.add({ resolve, reject })\n    })\n  }\n\n  /**\n   * Sends a bidi request\n   * @param params\n   * @returns {Promise<unknown>}\n   */\n  async send(params) {\n    if (this._closed) {\n      throw new Error('BiDi connection is closed')\n    }\n    if (!this.connected) {\n      await this.waitForConnection()\n    }\n    // Defense in depth: even after waitForConnection() resolves, the socket\n    // may have transitioned to CLOSING/CLOSED (e.g. caller closed the raw\n    // socket). Refuse rather than throwing from inside ws.send().\n    if (this._ws.readyState !== WebSocket.OPEN) {\n      throw new Error('BiDi connection is not open')\n    }\n\n    const id = ++this.id\n\n    this._ws.send(JSON.stringify({ id, ...params }))\n\n    return new Promise((resolve, reject) => {\n      const timeoutId = setTimeout(() => {\n        this._pending.delete(id)","sourceCodeStart":187,"sourceCodeEnd":223,"githubUrl":"https://github.com/SeleniumHQ/selenium/blob/aa36b38e696a0909e973bdf5e2f9031ffe842c4b/javascript/selenium-webdriver/bidi/index.js#L187-L223","documentation":"Thrown by `BiDi.send()` (and `waitForConnection()`) when `this._closed` is true — i.e. the connection has been permanently closed. The `_closed` flag is set by `_failPending()`, which is triggered by `close()`, the WebSocket 'close' event (peer/server disconnected), or the 'error' event. Once closed, every subsequent BiDi command throws this synchronously; it is terminal for that connection instance.","triggerScenarios":"Calling any BiDi method after `driver.quit()` or after the WebSocket dropped; reusing a BiDi handle across a navigation that severed the socket; calling send after an explicit `bidi.close()`; an async event handler issuing a command after teardown.","commonSituations":"Test teardown calls `driver.quit()` but an async network/log listener still issues a BiDi command; flaky CI where the Grid drops the WebSocket; commands issued after a tab/window close severed the BiDi session.","solutions":["Do not issue BiDi commands after driver.quit()/bidi.close() — tear down listeners first","Re-acquire the BiDi connection via driver.getBidi() after a reconnect/restart if the session is still alive","For event-driven code, guard with the connection state before sending","Catch the error and stop/skip the operation rather than blindly retrying on a dead connection"],"exampleFix":"// before\ndriver.quit()\nawait bidi.send({ method: 'session.status', params: {} }) // throws: connection is closed\n\n// after — stop BiDi work before quitting\nawait networkInspector.clearListeners()\ndriver.quit()","handlingStrategy":"try-catch","validationCode":"if (bidi.isConnected && bidi.socket && bidi.socket.readyState === 1 /* OPEN */) {\n  await bidi.send(cmd)\n} else {\n  // connection gone — reconnect via driver.getBidi() or skip\n}","typeGuard":"const isBiDiOpen = (bidi) => bidi.isConnected && bidi.socket?.readyState === 1","tryCatchPattern":"try {\n  await bidi.send(cmd)\n} catch (e) {\n  if (/connection is closed/i.test(e.message)) {\n    // session ended — stop BiDi work, do not retry blindly\n    return\n  }\n  throw e\n}","preventionTips":["Tear down all BiDi listeners/interceptors before driver.quit()","Treat 'connection is closed' as terminal — reconnect via driver.getBidi() if the session is alive, otherwise stop","Track session lifecycle so async handlers don't outlive the connection"],"tags":["bidi","websocket","connection","runtime","lifecycle"],"backgroundTag":null,"analyzedSha":"aa36b38e696a0909e973bdf5e2f9031ffe842c4b","analyzedAt":"2026-08-14T02:32:32.244Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}