chenhg5/cc-connect · error
wecom-ws: connection closed
Error message
wecom-ws: connection closed
What it means
When the WebSocket connection drops, runConnection's cleanup drains all pendingAck channels, delivering wsAckResult{err: 'wecom-ws: connection closed'} so callers waiting for reply/send acks fail fast instead of blocking forever. Senders blocked on an ack channel receive this error. Non-blocking send (select/default) means a channel nobody reads is simply discarded.
Source
Thrown at platform/wecom/websocket.go:214
p.mu.Lock()
p.conn = conn
p.mu.Unlock()
defer func() {
p.mu.Lock()
p.conn = nil
p.mu.Unlock()
conn.Close()
// Drain pending ACK channels so waiting goroutines are unblocked
// and stale entries do not accumulate across reconnections.
// Collect keys first, then delete — Range+Delete in callback is
// not guaranteed safe by the sync.Map contract.
var staleKeys []any
p.pendingAcks.Range(func(key, value any) bool {
if ch, ok := value.(chan wsAckResult); ok {
select {
case ch <- wsAckResult{err: fmt.Errorf("wecom-ws: connection closed")}:
default:
}
}
staleKeys = append(staleKeys, key)
return true
})
for _, k := range staleKeys {
p.pendingAcks.Delete(k)
}
}()
// Send subscribe (auth) frame
// Format: { cmd: "aibot_subscribe", headers: { req_id }, body: { bot_id, secret } }
subReqID := p.generateReqID("aibot_subscribe")
subFrame := map[string]any{
"cmd": "aibot_subscribe",
"headers": map[string]string{"req_id": subReqID},
"body": map[string]string{View on GitHub (pinned to 4000b2338a)
Solutions
- Treat it as transient: reconnect happens automatically via connectLoop; retry the send after reconnection.
- Buffer or queue outbound messages and flush them once the 'subscribed successfully' log appears.
- Check reconnect stability (frequent drops) — inspect preceding 'dial:'/'read:' errors for the root cause.
- Ensure callers of the ack channels handle the error return rather than waiting indefinitely.
Example fix
// before
result := <-ackCh // may block or get closed-chan zero value
// after
result, ok := <-ackCh
if !ok || (result.err != nil) {
return fmt.Errorf("wecom-ws: send not acked: %w", result.err)
} Defensive patterns
Strategy: try-catch
Validate before calling
if !platform.IsConnected() { queueForLater(msg); return } // check connection state before sending Type guard
null
Try / catch
res, err := p.SendAndWaitAck(ctx, msg)
if errors.Is(err, errConnClosed) {
waitUntilSubscribed()
res, err = p.SendAndWaitAck(ctx, msg) // retry once after reconnect
} Prevention
- Queue outbound messages while disconnected and flush on 'subscribed successfully'
- Handle ack-wait errors instead of blocking forever
- Monitor reconnect frequency to detect unstable networks
When it happens
Trigger: A Send/Reply posted a frame with a req_id and is waiting on its ack channel when the connection dies (dial failure, read error, pong timeout) before the ack arrives.
Common situations: Network interruption while sending messages through the wecom-ws bot; server-side disconnect during a burst of sends; caller's ctx-less wait on a message that will never be acked after a drop.
Understand the failure class
- Connection failures: ECONNREFUSED, ECONNRESET, and friends — why connections get refused, reset, or dropped.
Related errors
AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06).
Data as JSON: /api/errors/77da2f6c2284717c.
Report an issue: GitHub.