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 = trueView on GitHub (pinned to 4ed7494a32)
Solutions
- Inspect the wrapped error to distinguish closed connection vs timeout and reconnect if needed
- Guard writes with a mutex or a dedicated writer goroutine
- Check connection health (ping/pong) before sending large payloads
- 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
- Serialize binary writes like text writes (single writer/mutex)
- Chunk large payloads and set sane write deadlines
- Detect dead peers with ping/pong before streaming binary data
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
- failed to send text message: %w
- failed to connect: %w
- get artist info: %w
- get artist url: %w
- writing image file: %w
AI-assisted analysis of navidrome/navidrome@4ed7494a32 (2026-09-01).
Data as JSON: /api/errors/d76c72de56f32cd5.
Report an issue: GitHub.