gorilla/websocket · warning

websocket: close sent

Error message

websocket: close sent

What it means

ErrCloseSent is returned when the application attempts to write a data message after a close message has already been sent on the connection. The library tracks close-handshake state; once the client sends Close, further message writes are invalid per RFC 6455.

Source

Thrown at conn.go:86

	BinaryMessage = 2

	// CloseMessage denotes a close control message. The optional message
	// payload contains a numeric code and text. Use the FormatCloseMessage
	// function to format a close message payload.
	CloseMessage = 8

	// PingMessage denotes a ping control message. The optional message payload
	// is UTF-8 encoded text.
	PingMessage = 9

	// PongMessage denotes a pong control message. The optional message payload
	// is UTF-8 encoded text.
	PongMessage = 10
)

// ErrCloseSent is returned when the application writes a message to the
// connection after sending a close message.
var ErrCloseSent = errors.New("websocket: close sent")

// ErrReadLimit is returned when reading a message that is larger than the
// read limit set for the connection.
var ErrReadLimit = errors.New("websocket: read limit exceeded")

// netError satisfies the net Error interface.
type netError struct {
	msg       string
	temporary bool
	timeout   bool
}

func (e *netError) Error() string   { return e.msg }
func (e *netError) Temporary() bool { return e.temporary }
func (e *netError) Timeout() bool   { return e.timeout }

// CloseError represents a close message.
type CloseError struct {

View on GitHub (pinned to e064f32e36)

Solutions

  1. Treat ErrCloseSent as a normal shutdown signal — stop writing and return without treating it as fatal
  2. Coordinate goroutines with context cancellation or a done channel so no writes occur after CloseMessage
  3. Use a mutex or single-writer goroutine pattern to serialize all writes
  4. Check conn.WriteMessage errors with errors.Is(err, websocket.ErrCloseSent) to branch shutdown logic

Example fix

// before
if err := conn.WriteMessage(websocket.TextMessage, data); err != nil {
    log.Fatal(err)
}
// after
if err := conn.WriteMessage(websocket.TextMessage, data); err != nil {
    if errors.Is(err, websocket.ErrCloseSent) {
        return nil // normal during shutdown
    }
    return err
}
Defensive patterns

Strategy: try-catch

Type guard

func isCloseSent(err error) bool {
    return errors.Is(err, websocket.ErrCloseSent)
}

Try / catch

if err := conn.WriteMessage(websocket.TextMessage, data); err != nil {
    if errors.Is(err, websocket.ErrCloseSent) {
        return nil // shutdown in progress; not a failure
    }
    return err
}

Prevention

When it happens

Trigger: Calling WriteMessage/WritePreparedMessage (or NextWriter-based writes) after calling CloseMessage or a Close with a code/reason; also triggered by the library internally after close frames. Control frames Pong/Ping may still be permitted per spec nuances.

Common situations: Concurrent goroutines where one sends a close while another is still writing messages; application teardown order closing the connection before flushing pending writes; read pump error handlers sending close then the write pump continuing.

Related errors


AI-assisted analysis of gorilla/websocket@e064f32e36 (2026-08-31). Data as JSON: /api/errors/319f015f1407daab. Report an issue: GitHub.