chenhg5/cc-connect · warning
-2
-2
Error message
weixin: sendMessage throttled by ilink (ret=-2); the bot is rate-limited and sending during the penalty escalates it, retry the message later: %w
What it means
The ilink sendMessage backend returned ret=-2, meaning the bot is rate-limited. The library detects this with isSendThrottled and enriches the error: sending more messages during the penalty window escalates the penalty, so the send must be deferred.
Source
Thrown at platform/weixin/media_outbound.go:139
return err
}
msg := sendMessageReq{
Msg: weixinOutboundMsg{
FromUserID: "",
ToUserID: rc.peerUserID,
ClientID: "cc-" + randomHex(8),
MessageType: messageTypeBot,
MessageState: messageStateFinish,
ItemList: []messageItem{item},
ContextToken: rc.contextToken,
},
}
err := p.api.sendMessage(ctx, &msg)
if err == nil {
return nil
}
if isSendThrottled(err) {
return fmt.Errorf("weixin: sendMessage throttled by ilink (ret=-2); "+
"the bot is rate-limited and sending during the penalty escalates it, retry the message later: %w", err)
}
return err
}
func mediaFromUploadRef(ref *cdnUploadedRef) *cdnMedia {
return &cdnMedia{
EncryptQueryParam: ref.downloadParam,
AESKey: formatAesKeyForAPI(ref.aesKey),
EncryptType: 1,
}
}
func buildVideoMessageItem(ref *cdnUploadedRef) messageItem {
return messageItem{
Type: messageItemVideo,
VideoItem: &videoItem{
Media: mediaFromUploadRef(ref),View on GitHub (pinned to 4000b2338a)
Solutions
- Stop sending immediately and retry the message after a backoff (the penalty window must lapse).
- Add exponential backoff + jitter with a dedicated rate limiter for weixin sends.
- Do NOT retry in a tight loop — sending during the penalty escalates the restriction.
- Throttle outbound media volume proactively to stay under ilink limits.
Example fix
// before
for _, img := range images { go p.SendImage(ctx, rc, img) } // triggers ret=-2
// after
limiter := rate.NewLimiter(rate.Every(2*time.Second), 1)
for _, img := range images {
limiter.Wait(ctx)
if err := p.SendImage(ctx, rc, img); err != nil && isSendThrottled(err) {
time.Sleep(5 * time.Minute) // back off hard
}
} Defensive patterns
Strategy: retry
Try / catch
if err := p.SendImage(ctx, rc, img); err != nil && isSendThrottled(err) {
time.Sleep(penaltyBackoff) // minutes, not seconds; sending now escalates
retry(ctx, img)
} Prevention
- Rate-limit all weixin outbound sends with a token bucket
- Never retry immediately on ret=-2 — it escalates the penalty
- Use exponential backoff with jitter
- Spread bulk media sends over time
When it happens
Trigger: sendSingleItem calling p.api.sendMessage which returns an error matched by isSendThrottled (ret=-2) while sending an image/file/audio item.
Common situations: Burst media sends to the same peer; scheduled broadcasts hitting WeChat rate limits; a bot that kept retrying during a penalty and escalated it.
Related errors
- weixin: push budget exhausted (%d push messages in the last
- -2
- bind mode requires --token
- new/QR mode does not accept --token; use `cc-connect weixin
- range chunk retries exhausted
AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06).
Data as JSON: /api/errors/e4ce47f62bcf6d56.
Report an issue: GitHub.