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

  1. Treat it as transient: reconnect happens automatically via connectLoop; retry the send after reconnection.
  2. Buffer or queue outbound messages and flush them once the 'subscribed successfully' log appears.
  3. Check reconnect stability (frequent drops) — inspect preceding 'dial:'/'read:' errors for the root cause.
  4. 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

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

Related errors


AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06). Data as JSON: /api/errors/77da2f6c2284717c. Report an issue: GitHub.