{"record":{"id":"d78fe2d94e8928db","repo":"microsoft/playwright","slug":"unexpected-websocket-state-this-ws-readystate","errorCode":null,"errorMessage":"Unexpected WebSocket state: ${this._ws.readyState}","messagePattern":"Unexpected WebSocket state: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/playwright-core/src/tools/mcp/cdpRelay.ts","lineNumber":309,"sourceCode":"\nclass ExtensionConnection {\n  private readonly _ws: WebSocket;\n  private readonly _callbacks = new Map<number, { resolve: (o: any) => void, reject: (e: Error) => void, error: Error }>();\n  private _lastId = 0;\n\n  onmessage?: <M extends keyof ExtensionEventsV2>(method: M, params: ExtensionEventsV2[M]['params']) => void;\n  onclose?: (reason: string) => void;\n\n  constructor(ws: WebSocket) {\n    this._ws = ws;\n    this._ws.on('message', this._onMessage.bind(this));\n    this._ws.on('close', this._onClose.bind(this));\n    this._ws.on('error', this._onError.bind(this));\n  }\n\n  async send<M extends keyof ExtensionCommandV2>(method: M, params: ExtensionCommandV2[M]['params']): Promise<any> {\n    if (this._ws.readyState !== ws.OPEN)\n      throw new Error(`Unexpected WebSocket state: ${this._ws.readyState}`);\n    const id = ++this._lastId;\n    this._ws.send(JSON.stringify({ id, method, params }));\n    const error = new Error(`Protocol error: ${method}`);\n    return new Promise((resolve, reject) => {\n      this._callbacks.set(id, { resolve, reject, error });\n    });\n  }\n\n  close(message: string) {\n    debugLogger('closing extension connection:', message);\n    if (this._ws.readyState === ws.OPEN)\n      this._ws.close(1000, message);\n  }\n\n  private _onMessage(event: websocket.RawData) {\n    const eventData = event.toString();\n    let parsedJson;\n    try {","sourceCodeStart":291,"sourceCodeEnd":327,"githubUrl":"https://github.com/microsoft/playwright/blob/c8fc3bf8d31542d59b4d4d9eaab1df93ff541dc6/packages/playwright-core/src/tools/mcp/cdpRelay.ts#L291-L327","documentation":"Thrown by the relay's `ExtensionConnection.send` when the underlying WebSocket's `readyState` is not `OPEN`. The guard prevents queueing a send on a closing/closed socket. The numeric readyState (e.g. 2=CLOSING, 3=CLOSED) is included for diagnosis.","triggerScenarios":"A command is dispatched to the extension after the WebSocket has started closing or fully closed — e.g. the extension disconnected, the relay called `close()`, or a network error transitioned the socket out of OPEN. Any in-flight or subsequent `send` then hits this guard.","commonSituations":"The user closed the browser/extension mid-session; an idle timeout or network drop closed the WebSocket; a protocol error triggered `close()` and a queued command fires afterward; the extension was reloaded.","solutions":["Re-establish the connection: restart the relay and re-run `establishExtensionConnection` before sending more commands.","Treat readyState!=OPEN as a signal to tear down and reconnect rather than retrying the same send.","Ensure no commands are queued after intentionally calling `close()` on the connection."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Guard sends against non-OPEN sockets.\nimport ws from 'ws';\nfunction assertOpen(socket: ws) {\n  if (socket.readyState !== ws.OPEN)\n    throw new Error(`WebSocket not open (state=${socket.readyState}); reconnect first.`);\n}","typeGuard":null,"tryCatchPattern":"try {\n  await conn.send(method, params);\n} catch (e) {\n  if (/Unexpected WebSocket state/.test((e as Error).message)) {\n    await relay.establishExtensionConnection(clientName); // reconnect\n    await conn.send(method, params);\n  } else throw e;\n}","preventionTips":["Check readyState===OPEN before sending, or rely on a connection-ready gate.","Treat any non-OPEN state as 'reconnect required', not 'retry send'.","Drain queued commands on close rather than firing them into a dead socket."],"tags":["extension","websocket","cdp-relay","connection","state"],"backgroundTag":null,"analyzedSha":"c8fc3bf8d31542d59b4d4d9eaab1df93ff541dc6","analyzedAt":"2026-08-12T07:26:36.950Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}