chenhg5/cc-connect · error

-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

sendChunk detects ilink's burst-throttle response (ret=-2) via isSendThrottled and converts it into a fail-fast error explaining that continuing to send during the penalty window escalates the rate limit. The original API error is wrapped for context.

Solutions

  1. Stop sending immediately and wait out the penalty window before retrying
  2. Enforce/lower the push quota (checkSendQuota) so ret=-2 is never reached
  3. Consolidate notifications into fewer, larger messages per window
  4. Back off exponentially in any retry loop instead of retrying promptly
Defensive patterns

Strategy: retry

Validate before calling

// avoid triggering ret=-2: keep pushes under the quota window
if pushesInWindow >= 5 {
    deferSendUntilWindowEnd(msg)
    return nil
}

Try / catch

if err := p.Send(ctx, rc, msg); err != nil && strings.Contains(err.Error(), "throttled by ilink") {
    log.Warn("ilink penalty active; backing off long", "err", err)
    time.Sleep(penaltyBackoff) // minutes, not seconds
    return err // do NOT hot-retry; escalate the throttle
}

Prevention

When it happens

Trigger: p.api.sendText returns an error whose response contains ret=-2, typically after the bot exceeded roughly 5-6 pushes per window and ilink imposed a penalty period.

Common situations: Cron/timer pushes stacking up faster than the window allows; retry loops hammering the API during an active penalty and extending it; multiple sessions pushing to the same account concurrently.

Related errors


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

Appendix: source

Thrown at platform/weixin/weixin.go:876

// isSendThrottled reports whether err is ilink sendmessage's burst-throttle
// response (ret=-2 "prepare failed"). This is a bot-wide rate-limit penalty, not a
// context_token problem: the gateway accepts any (or no) context_token on sends.
func isSendThrottled(err error) bool {
	return err != nil && strings.Contains(err.Error(), "ret=-2")
}

// sendChunk sends a single chunk. If ilink throttles the send (ret=-2
// "prepare failed"), it fails fast instead of retrying: live testing showed the
// penalty is escalated by every send attempt made while it is active, so retrying
// (e.g. the old 3×500ms loop plus the extra notice send) only prolongs the outage.
func (p *Platform) sendChunk(ctx context.Context, rc *replyContext, chunk string) error {
	clientID := "cc-" + randomHex(6)
	err := p.api.sendText(ctx, rc.peerUserID, chunk, rc.contextToken, clientID)
	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 truncatePreview(s string, max int) string {
	if len(s) <= max {
		return s
	}
	return s[:max] + "..."
}

func splitUTF8(s string, maxRunes int) []string {
	if maxRunes <= 0 || utf8.RuneCountInString(s) <= maxRunes {
		return []string{s}
	}
	var out []string
	runes := []rune(s)

View on GitHub (pinned to 4000b2338a)