SeleniumHQ/selenium · error · Error
BiDi connection is not open
Error message
BiDi connection is not open
What it means
Thrown by `BiDi.send()` as a defense-in-depth check: after `waitForConnection()` resolves, the method re-checks `this._ws.readyState !== WebSocket.OPEN`. This catches the race where the socket transitioned to CLOSING/CLOSED between the handshake completing and the send (e.g. the caller closed the raw socket, or the peer dropped immediately after opening). Unlike [38], this can fire even though `_closed` is not yet set, because the close/error handler may not have run yet.
Source
Thrown at javascript/selenium-webdriver/bidi/index.js:214
}
/**
* Sends a bidi request
* @param params
* @returns {Promise<unknown>}
*/
async send(params) {
if (this._closed) {
throw new Error('BiDi connection is closed')
}
if (!this.connected) {
await this.waitForConnection()
}
// Defense in depth: even after waitForConnection() resolves, the socket
// may have transitioned to CLOSING/CLOSED (e.g. caller closed the raw
// socket). Refuse rather than throwing from inside ws.send().
if (this._ws.readyState !== WebSocket.OPEN) {
throw new Error('BiDi connection is not open')
}
const id = ++this.id
this._ws.send(JSON.stringify({ id, ...params }))
return new Promise((resolve, reject) => {
const timeoutId = setTimeout(() => {
this._pending.delete(id)
reject(new Error(`Request with id ${id} timed out`))
}, RESPONSE_TIMEOUT)
this._pending.set(id, { resolve, reject, timeoutId })
})
}
/**
* Subscribe to eventsView on GitHub (pinned to aa36b38e69)
Solutions
- Avoid closing the raw socket (`bidi.socket`) directly — use `bidi.close()` which coordinates teardown
- Serialize teardown and BiDi commands so close() cannot race an in-flight send()
- Retry the command after re-acquiring the connection if the session is still alive
Example fix
// before — concurrent raw close races the send bidi.socket.close() // raw close await bidi.send(cmd) // may throw: connection is not open // after — coordinated close await bidi.close() // fails pending sends cleanly
Defensive patterns
Strategy: try-catch
Validate before calling
if (bidi.socket && bidi.socket.readyState === 1 /* OPEN */) {
await bidi.send(cmd)
} Type guard
const isSocketOpen = (bidi) => bidi.socket?.readyState === 1
Try / catch
try {
await bidi.send(cmd)
} catch (e) {
if (/connection is not open/i.test(e.message)) {
// transient race — reconnect via driver.getBidi() then retry once
return
}
throw e
} Prevention
- Never call bidi.socket.close() directly — use bidi.close() to coordinate teardown
- Serialize connection teardown against in-flight commands
- Treat 'not open' as a transient race (reconnect + retry once), unlike the terminal 'is closed'
When it happens
Trigger: A concurrent caller closes the raw socket (`bidi.socket.close()`) while another send() is racing through waitForConnection(); the peer server closes the WebSocket immediately after the handshake; tight interleaving between close() and send().
Common situations: Concurrent BiDi traffic during teardown; an interceptor's handler closes the socket while another command is in-flight; server-side connection limits dropping the socket right after open.
Related errors
- BiDi connection is closed
- timeout must be a positive number
- interval must be a positive number
- Timed out waiting for response to BiDi command {current_id}
- #{message['error']}: #{message['message']} #{message['stackt
AI-assisted analysis of SeleniumHQ/selenium@aa36b38e69 (2026-08-14).
Data as JSON: /api/errors/b77398d2253db8d7.
Report an issue: GitHub.