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
- Treat ErrCloseSent as a normal shutdown signal — stop writing and return without treating it as fatal
- Coordinate goroutines with context cancellation or a done channel so no writes occur after CloseMessage
- Use a mutex or single-writer goroutine pattern to serialize all writes
- 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
- Centralize all writes in one goroutine; close the channel to signal shutdown
- Cancel writers via context before calling CloseMessage
- Never write after the read pump reports a closure error
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
- websocket: write closed
- concurrent write to websocket connection
- websocket: bad handshake
- websocket: invalid compression negotiation
- malformed ws or wss URL
AI-assisted analysis of gorilla/websocket@e064f32e36 (2026-08-31).
Data as JSON: /api/errors/319f015f1407daab.
Report an issue: GitHub.