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

  1. Stop sending immediately and retry the message after a backoff (the penalty window must lapse).
  2. Add exponential backoff + jitter with a dedicated rate limiter for weixin sends.
  3. Do NOT retry in a tight loop — sending during the penalty escalates the restriction.
  4. 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

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


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