siyuan-note/siyuan · warning
WebSocket is not open (state: %d)
Error message
WebSocket is not open (state: %d)
What it means
Returned by the WebSocket port.send() path when the socket readyState is CLOSING (2) or CLOSED (3) at send time. The kernel refuses to enqueue a WriteAsync in those states and rejects the send Promise with the current state number.
Source
Thrown at kernel/plugin/plugin.go:1144
sendRunErr := p.worker.Run(func(rt *goja.Runtime) (_ any, err error) {
var messageData []byte
var opcode gws.Opcode
if len(sendCall.Arguments) >= 1 {
data := sendCall.Argument(0)
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())
}
}
state := WebSocketState(readyState.Load())
if state == WebSocketReadyStateClosing || state == WebSocketReadyStateClosed {
err = fmt.Errorf("WebSocket is not open (state: %d)", state)
return
}
updatePortBufferedAmount(rt, len(messageData))
socket.WriteAsync(opcode, messageData, func(writeErr error) {
p.worker.Run(func(rt *goja.Runtime) (_ any, err error) {
if writeErr == nil {
updatePortBufferedAmount(rt, -len(messageData))
} else {
err = writeErr
}
return
}, func(rt *goja.Runtime, result any, err error) {
if lo.IsNil(err) {
if resolveErr := sendResolve(result); resolveErr != nil {
logging.LogErrorf("[plugin:%s] ws server port.send resolve: %v", p.Name, resolveErr)
}
} else {View on GitHub (pinned to 251596fc0d)
Solutions
- Check port.readyState === 1 (OPEN) before calling port.send.
- Stop send loops on onclose/onerror; drain queued sends before reopening.
- Catch the rejection and treat state 2/3 as a non-fatal 'connection gone' signal.
Example fix
// before
port.send(JSON.stringify(msg));
// after
if (port.readyState !== 1) return;
port.send(JSON.stringify(msg)).catch((e) => {
if (/state: [23]/.test(String(e))) return; // already closing/closed
throw e;
}); Defensive patterns
Strategy: validation
Validate before calling
const OPEN = 1; if (port.readyState !== OPEN) { /* skip or buffer */ return; } Type guard
function isPortOpen(port: { readyState: number }): boolean { return port.readyState === 1; } Try / catch
try { await port.send(payload); } catch (e) { if (/state: [23]/.test(String(e))) return; throw e; } Prevention
- Check readyState === 1 before each send.
- Stop send loops in onclose/onerror.
- Tolerate state 2/3 rejections as 'connection gone'.
When it happens
Trigger: Plugin calls port.send(...) after onclose fired, or a client disconnect/cancel races with the send so the state transitions to closing before the worker runs.
Common situations: Sending from a timer/loop without checking readyState, or sending in onclose/onerror handlers after the connection is already shutting down.
Related errors
- failed to convert request value to object
- WebSocket is closing
- WebSocket is closed
- WebSocket is not open (state: %d)
- fsnotify watcher not initialized
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/deb7a70358425a2a.
Report an issue: GitHub.