chenhg5/cc-connect · warning

wecom-ws: ack timeout

Error message

wecom-ws: ack timeout

What it means

errWSAckTimeout is the sentinel for the WeCom (WeChat Work) websocket client: after sending a frame with a req_id, no ack/result frame arrived within the timeout (5s normal, 30s media). It is treated as a soft failure — writeAndWaitAckWithTimeout logs at debug and returns nil, proceeding without confirmation, while strict variants convert it into a descriptive error for the caller.

Source

Thrown at platform/wecom/websocket.go:47

	secret      string
	allowFrom   string
	conn        *websocket.Conn
	handler     core.MessageHandler
	ctx         context.Context
	cancel      context.CancelFunc
	mu          sync.Mutex // protects conn writes
	dedup       core.MessageDedup
	reqSeq      atomic.Int64 // monotonic counter for generating unique req_id
	missedPong  atomic.Int32 // consecutive heartbeat acks not received
	pendingAcks sync.Map     // req_id -> chan wsAckResult, for sequential send with ack waiting
}

const (
	wsAckTimeout      = 5 * time.Second
	wsMediaAckTimeout = 30 * time.Second
)

var errWSAckTimeout = errors.New("wecom-ws: ack timeout")

// wsReplyContext holds the context needed to reply to a specific message.
type wsReplyContext struct {
	reqID    string // req_id from headers of aibot_msg_callback
	chatID   string // chatid for aibot_send_msg
	chatType string // chattype: "single" or "group"
	userID   string // from.userid
}

// --- WebSocket protocol frame types (matching official SDK) ---

// wsFrame is the unified frame structure used for all WebSocket communication.
// Format: { cmd, headers: { req_id }, body: {...} }
// Response frames may omit cmd and include errcode/errmsg instead.
type wsFrame struct {
	Cmd     string          `json:"cmd,omitempty"`
	Headers wsFrameHeaders  `json:"headers"`
	Body    json.RawMessage `json:"body,omitempty"`

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Check the debug log line 'wecom-ws: ack timeout, proceeding' frequency — if messages are being lost, treat it as a real delivery problem and inspect the WeCom service status.
  2. Verify network stability to the WeCom gateway (persistent websocket, no aggressive NAT/firewall idle timeouts); enable keepalive/ping.
  3. Increase wsAckTimeout/wsMediaAckTimeout if your uplink is slow, especially for media sends.
  4. If strict mode reports it, retry the send with a fresh req_id after confirming the connection is alive (or reconnect first).

Example fix

// before
if err := p.writeAndWaitAckStrict(ctx, frame, reqID, wsAckTimeout); err != nil { return err }
// after
if err := p.writeAndWaitAckStrict(ctx, frame, reqID, wsAckTimeout); err != nil {
    if errors.Is(err, errWSAckTimeout) {
        p.reconnectIfNeeded()
        return p.writeAndWaitAckStrict(ctx, frame, reqID, wsAckTimeout)
    }
    return err
}
Defensive patterns

Strategy: try-catch

Validate before calling

// before sending, confirm the websocket is still alive:
if p.conn == nil || time.Since(p.lastPong) > 2*wsAckTimeout {
    if err := p.reconnect(ctx); err != nil {
        return fmt.Errorf("wecom-ws: connection not ready: %w", err)
    }
}

Try / catch

err := p.writeAndWaitAckStrict(ctx, frame, reqID, wsAckTimeout)
if errors.Is(err, errWSAckTimeout) {
    slog.Warn("wecom-ws: ack not received", "req_id", reqID)
    p.reconnectIfNeeded()
    // retry once with a fresh req_id
    return p.writeAndWaitAckStrict(ctx, frame, newReqID(), wsAckTimeout)
}

Prevention

When it happens

Trigger: writeAndWaitResult times out waiting for the ack keyed by reqID: slow or overloaded WeCom aibot gateway; network drop between client and WeCom; sending frames when the websocket connection is half-dead; media uploads exceeding wsMediaAckTimeout (30s).

Common situations: WeCom service degradation or throttling; corporate firewalls dropping long-lived websocket connections; message bursts where acks are processed out of order or slowly; media (image/file) uploads on slow uplinks.

Understand the failure class

Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.

Related errors


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