navidrome/navidrome · error

failed to send binary message: %w

Error message

failed to send binary message: %w

What it means

SendBinary writes a binary frame via conn.WriteMessage(websocket.BinaryMessage, data); any write failure is wrapped as "failed to send binary message: %w". Like SendText, it fails when the connection is dead, closing, or being written concurrently.

Source

Thrown at plugins/host_websocket.go:165

	if err != nil {
		return err
	}

	if err := wsConn.conn.WriteMessage(websocket.TextMessage, []byte(message)); err != nil {
		return fmt.Errorf("failed to send text message: %w", err)
	}

	return nil
}

func (s *webSocketServiceImpl) SendBinary(ctx context.Context, connectionID string, data []byte) error {
	wsConn, err := s.getConnection(connectionID)
	if err != nil {
		return err
	}

	if err := wsConn.conn.WriteMessage(websocket.BinaryMessage, data); err != nil {
		return fmt.Errorf("failed to send binary message: %w", err)
	}

	return nil
}

func (s *webSocketServiceImpl) CloseConnection(ctx context.Context, connectionID string, code int32, reason string) error {
	s.mu.Lock()
	wsConn, exists := s.connections[connectionID]
	if !exists {
		s.mu.Unlock()
		return fmt.Errorf("connection ID %q not found", connectionID)
	}
	delete(s.connections, connectionID)
	s.mu.Unlock()

	// Mark as closed to prevent callback
	wsConn.closeMu.Lock()
	wsConn.isClosed = true

View on GitHub (pinned to 4ed7494a32)

Solutions

  1. Inspect the wrapped error to distinguish closed connection vs timeout and reconnect if needed
  2. Guard writes with a mutex or a dedicated writer goroutine
  3. Check connection health (ping/pong) before sending large payloads
  4. Set appropriate write deadlines and chunk very large payloads

Example fix

// before
err := ws.SendBinary(ctx, id, payload)
// after
if err := ws.SendBinary(ctx, id, payload); err != nil {
    if isConnClosed(err) {
        id = mustReconnect()
        err = ws.SendBinary(ctx, id, payload)
    }
}
Defensive patterns

Strategy: retry

Validate before calling

if len(data) == 0 { return errors.New("empty binary payload") }
if !isAlive(connectionID) { return errors.New("connection not alive; reconnect first") }

Type guard

func isWriteErrRecoverable(err error) bool {
    return errors.Is(err, net.ErrClosed) || strings.Contains(err.Error(), "broken pipe") || strings.Contains(err.Error(), "write timeout")
}

Try / catch

err := ws.SendBinary(ctx, id, data)
if err != nil {
    if isWriteErrRecoverable(err) {
        id = reconnect()
        err = ws.SendBinary(ctx, id, data)
    }
    if err != nil { return err }
}

Prevention

When it happens

Trigger: Calling SendBinary on a closed/torn-down connection, during network interruption, or from multiple goroutines without coordination; also very large payloads causing write timeouts.

Common situations: Streaming binary data (files, protobuf) after the server dropped the link; peer sent close frame but app kept sending; write deadline expired.

Related errors


AI-assisted analysis of navidrome/navidrome@4ed7494a32 (2026-09-01). Data as JSON: /api/errors/d76c72de56f32cd5. Report an issue: GitHub.