chenhg5/cc-connect · error
wecom-ws: ack error: errcode=%d errmsg=%s
Error message
wecom-ws: ack error: errcode=%d errmsg=%s
What it means
handleFrame dispatches ack frames for outstanding reply/send requests (matched by req_id). When the ack frame carries a non-zero errcode, this error is built with the server's errcode/errmsg and delivered to the waiting caller via dispatchAck. It means the specific message send/reply was rejected by WeCom at the application level — the connection and subscription are fine.
Source
Thrown at platform/wecom/websocket.go:304
switch frame.Cmd {
case "aibot_msg_callback":
p.handleMsgCallback(frame)
case "aibot_event_callback":
slog.Debug("wecom-ws: event callback received (ignored)", "req_id", frame.Headers.ReqID)
case "":
// Response frame (no cmd): identify by req_id prefix
reqID := frame.Headers.ReqID
switch {
case strings.HasPrefix(reqID, "ping"):
p.missedPong.Store(0)
slog.Debug("wecom-ws: heartbeat ack received")
case strings.HasPrefix(reqID, "aibot_subscribe"):
// Late subscribe ack (should have been consumed in runConnection)
slog.Debug("wecom-ws: late subscribe ack")
default:
var ackErr error
if frame.ErrCode != nil && *frame.ErrCode != 0 {
ackErr = fmt.Errorf("wecom-ws: ack error: errcode=%d errmsg=%s", *frame.ErrCode, frame.ErrMsg)
slog.Warn("wecom-ws: reply/send ack error", "req_id", reqID, "errcode", *frame.ErrCode, "errmsg", frame.ErrMsg)
} else {
slog.Debug("wecom-ws: reply/send ack ok", "req_id", reqID)
}
p.dispatchAck(reqID, wsAckResult{frame: frame, err: ackErr})
}
default:
slog.Debug("wecom-ws: unhandled cmd", "cmd", frame.Cmd)
}
}
func (p *WSPlatform) dispatchAck(reqID string, result wsAckResult) {
ch, ok := p.pendingAcks.LoadAndDelete(reqID)
if !ok {
return
}
resultCh, ok := ch.(chan wsAckResult)
if !ok {View on GitHub (pinned to 4000b2338a)
Solutions
- Log/inspect errcode and look it up in WeCom's error code docs to identify the rejection reason.
- Verify the target user/chat ID is valid and the bot is permitted to message it (user must have interacted with the bot first for 1:1 messages).
- Check message content for prohibited or oversized payload rejected by the server.
- Surface the error to the caller (dispatchAck already does) and add retry logic only for retryable errcodes.
Example fix
// before
p.dispatchAck(reqID, wsAckResult{frame: frame, err: ackErr}) // caller ignores ackErr
// after
res := <-ackCh
if res.err != nil {
slog.Error("wecom send rejected", "req_id", reqID, "err", res.err)
return res.err
} Defensive patterns
Strategy: try-catch
Validate before calling
// before sending: ensure target is valid and has interacted with the bot
if !botKnowsUser(personID) { return errors.New("user must message the bot first") } Type guard
null
Try / catch
res, err := p.SendAndWaitAck(ctx, msg)
if res.err != nil {
switch {
case isRetryableAckCode(res.err): retryWithBackoff()
default: return fmt.Errorf("wecom rejected send: %w", res.err)
}
} Prevention
- Only message users who have initiated contact with the bot
- Validate target chat/user IDs before sending
- Keep message payloads within WeCom size/policy limits
- Always check ack results rather than fire-and-forget sends
When it happens
Trigger: A reply/send frame got an ack with errcode != 0 — e.g. invalid target chat/user ID, message content rejected, permission error, or expired req_id/session on the server side.
Common situations: Bot not authorized to message the target user (user never messaged the bot first); malformed markdown/text content rejected; sending to a chat the bot was removed from; content violating platform policies.
Related errors
- wecom-ws: ack timeout
- cloud_web: preview_ack timeout
- create device: %w
- dial: %w
- wecom-ws: connection closed
AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06).
Data as JSON: /api/errors/6bd5c68fbf57cfc9.
Report an issue: GitHub.