siyuan-note/siyuan · warning

WebSocket is closed

Error message

WebSocket is closed

What it means

Returned by socket.open() when readyState is CLOSED (3). The connection has finished closing; open() will not transition it back to open.

Source

Thrown at kernel/plugin/api_client.go:338

				})
			}

			ws_open := rt.ToValue(func(openCall goja.FunctionCall, rt *goja.Runtime) goja.Value {
				openPromise, openResolve, openReject := rt.NewPromise()

				openRunErr := p.worker.Run(func(rt *goja.Runtime) (_ any, err error) {
					state := WebSocketState(readyState.Load())
					switch state {
					case WebSocketReadyStateOpen:
						if resolveErr := openResolve(nil); resolveErr != nil {
							logging.LogErrorf("[plugin:%s] siyuan.client.socket.open resolve: %v", p.Name, resolveErr)
						}
						return
					case WebSocketReadyStateClosing:
						err = fmt.Errorf("WebSocket is closing")
						return
					case WebSocketReadyStateClosed:
						err = fmt.Errorf("WebSocket is closed")
						return
					}

					addOpenPromise(openResolve, openReject)
					openOnce.Do(func() {
						go func() {
							conn, _, dialErr := gws.NewClient(h, &gws.ClientOption{
								Addr:          wsURL,
								RequestHeader: wsHeader,
							})
							if dialErr != nil {
								p.worker.Run(func(rt *goja.Runtime) (_ any, _ error) {
									setReadyState(rt, WebSocketReadyStateClosed)

									event := rt.NewObject()
									event.Set("type", rt.ToValue("error"))
									event.Set("error", rt.NewGoError(dialErr))
									invokeHook(rt, "onerror", event)

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Create a new connection via siyuan.client.socket(path) and open() that, rather than reopening the closed one.
  2. Check readyState === CLOSED and branch to recreate.

Example fix

// before
// ws is already closed
await ws.open(); // rejects: WebSocket is closed
// after
const ws = await siyuan.client.socket('/ws/foo');
await ws.open();
Defensive patterns

Strategy: validation

Validate before calling

if (ws.readyState === 3) {
  ws = await siyuan.client.socket(path); // recreate
}
await ws.open();

Type guard

const isClosed = (ws) => ws.readyState === 3;

Try / catch

try { await ws.open(); }
catch (e) { if (/WebSocket is closed/.test(String(e))) { ws = await siyuan.client.socket(path); await ws.open(); } }

Prevention

When it happens

Trigger: Calling open() on a socket whose close handshake completed, or after the dial failed and readyState was set to CLOSED.

Common situations: Reconnect logic that calls open() on the original socket object instead of creating a new one; or calling open() after a failed dial.

Related errors


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