siyuan-note/siyuan · error
WebSocket not yet connected
Error message
WebSocket not yet connected
What it means
The ready state is OPEN but the underlying gws connection handle (gwsConn) has not been populated yet — a race between the JS-visible OPEN transition and the Go-side connection pointer being stored. send() finds c == nil and rejects with 'WebSocket not yet connected'.
Solutions
- Retry the send after a short delay or on the next tick; the window is transient.
- Serialize sends: perform the first send inside a setTimeout/onopen-followup rather than synchronously after open resolves.
- If it persists, the connection dropped between state update and send — recreate the socket.
Example fix
// before
await socket.open();
socket.send('hello');
// after
await socket.open();
setTimeout(() => {
if (socket.readyState === 1) socket.send('hello');
}, 0); Defensive patterns
Strategy: retry
Validate before calling
if (socket.readyState === 1) { /* still can race; add a tick delay before first send */ } Try / catch
try {
socket.send(first);
} catch (e) {
if (String(e).includes('not yet connected')) {
setTimeout(() => socket.readyState === 1 && socket.send(first), 50);
}
} Prevention
- Delay the first send until at least one event-loop tick after open resolves
- Serialize the first post-open message through a small queue
- Recreate the socket if the error repeats — the connection likely dropped
When it happens
Trigger: Calling send() immediately after the open promise resolves, before the internal connection reference is published; concurrent send during the connection handshake completion window.
Common situations: Code that sends a subscription/register message in the first microtask after open resolves; fast local connections where JS state flips before the atomic pointer store is observed.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/250f2640bd65d01b.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/plugin/api_client.go:432
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
}, func(rt *goja.Runtime, result any, err error) {
if lo.IsNil(err) {
if resolveErr := sendResolve(result); resolveErr != nil {
logging.LogErrorf("[plugin:%s] siyuan.client.socket.send resolve: %v", p.Name, resolveErr)
}
} else {View on GitHub (pinned to 9f775e8a12)