siyuan-note/siyuan · error

WebSocket is closing

Error message

WebSocket is closing

What it means

The WebSocket close was already initiated (ready state = CLOSING) when open() was called, so the binding rejects with 'WebSocket is closing' instead of registering a new open promise. A socket in CLOSING state will never transition to OPEN, so waiting would be pointless.

Solutions

  1. Check socket.readyState before calling open(); skip or wait when it is CLOSING.
  2. Wait for the close event (or onclose callback) to complete, then create a fresh socket and open it.
  3. Re-run the open on a new socket instance rather than reusing the closing one.

Example fix

// before
socket.open();
// after
if (socket.readyState === 0 || socket.readyState === 1) {
  // already connecting/open, do nothing
} else if (socket.readyState === 3) {
  socket = client.socket.connect('/ws');
  socket.open();
} // CLOSING (2): wait for close event before reopening
Defensive patterns

Strategy: type-guard

Validate before calling

if (socket.readyState === 2) { /* CLOSING: defer reopen until onclose */ }

Type guard

const canOpen = (s) => s.readyState === 3; // only CLOSED sockets may be replaced/reopened; CLOSING must wait

Try / catch

try {
  socket.open();
} catch (e) {
  if (String(e).includes('WebSocket is closing')) {
    socket.onclose = () => { socket = client.socket.connect(path); socket.open(); };
  }
}

Prevention

When it happens

Trigger: Calling socket.open() while a close() is in progress — e.g. an open retry racing with plugin shutdown, or calling open() from a close handler / during runtime teardown.

Common situations: Reconnect logic that calls open() without checking readyState after an error; asynchronous callbacks firing during window unload; plugin reload where the old socket is still closing.

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/a560f2bff87ee30f. Report an issue: GitHub.

Appendix: source

Thrown at kernel/plugin/api_client.go:335

			doClose := func() {
				closeOnce.Do(func() {
					cancel()
				})
			}

			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()

View on GitHub (pinned to 9f775e8a12)