siyuan-note/siyuan · warning

WebSocket is not open (state: %d)

Error message

WebSocket is not open (state: %d)

What it means

Returned by socket.send() when readyState is CLOSING (2) or CLOSED (3). send() refuses to queue a frame on a connection that is shutting down or gone, and rejects the send promise with the numeric state.

Source

Thrown at kernel/plugin/api_client.go:426

				var messageData []byte
				var opcode gws.Opcode
				if data := sendCall.Argument(0); isJsValueNotNull(data) {
					if arrayBuffer, ok := data.Export().(goja.ArrayBuffer); ok {
						opcode = gws.OpcodeBinary
						b := arrayBuffer.Bytes()
						messageData = make([]byte, len(b))
						copy(messageData, b) // ArrayBuffer.Bytes() points into JS engine memory; copy before async send
					} else {
						opcode = gws.OpcodeText
						messageData = []byte(data.String())
					}
				}

				sendRunErr := p.worker.Run(func(rt *goja.Runtime) (_ any, err error) {
					state := WebSocketState(readyState.Load())
					if state == WebSocketReadyStateClosing || state == WebSocketReadyStateClosed {
						err = fmt.Errorf("WebSocket is not open (state: %d)", state)
						return
					}

					c := gwsConn.Load()
					if c == nil {
						err = fmt.Errorf("WebSocket not yet connected")
						return
					}

					updateBufferedAmount(rt, len(messageData))
					c.WriteAsync(opcode, messageData, func(writeErr error) {
						p.worker.Run(func(rt *goja.Runtime) (_ any, err error) {
							if writeErr == nil {
								updateBufferedAmount(rt, -len(messageData))
							} else {
								err = writeErr
							}
							return

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Guard send() with ws.readyState === 1 (OPEN).
  2. Stop senders/heartbeat timers in onclose before they can fire again.

Example fix

// before
ws.close();
await ws.send('hi'); // rejects: WebSocket is not open (state: 2)
// after
if (ws.readyState === 1) {
  await ws.send('hi');
}
Defensive patterns

Strategy: validation

Validate before calling

async function safeSend(ws, data) {
  if (ws.readyState !== 1) return; // drop silently when not OPEN
  return ws.send(data);
}

Type guard

const isOpen = (ws) => ws.readyState === 1;

Try / catch

try { await ws.send(data); }
catch (e) { if (/WebSocket is not open/.test(String(e))) stopSenders(); }

Prevention

When it happens

Trigger: Calling send() after close() was called, or from a timer/handler that fires after the peer closed the connection.

Common situations: Heartbeat/keep-alive senders that keep firing after close; message sends racing with an onclose event.

Related errors


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/eea9d93a83cb6775. Report an issue: GitHub.