{"record":{"id":"f76f7df38d0632e1","repo":"chenhg5/cc-connect","slug":"2-f76f7d","errorCode":"-2","errorMessage":"weixin: sendMessage throttled by ilink (ret=-2); the bot is rate-limited and sending during the penalty escalates it, retry the message later: %w","messagePattern":"weixin: sendMessage throttled by ilink \\(ret=-2\\); the bot is rate-limited and sending during the penalty escalates it, retry the message later: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"platform/weixin/weixin.go","lineNumber":876,"sourceCode":"// isSendThrottled reports whether err is ilink sendmessage's burst-throttle\n// response (ret=-2 \"prepare failed\"). This is a bot-wide rate-limit penalty, not a\n// context_token problem: the gateway accepts any (or no) context_token on sends.\nfunc isSendThrottled(err error) bool {\n\treturn err != nil && strings.Contains(err.Error(), \"ret=-2\")\n}\n\n// sendChunk sends a single chunk. If ilink throttles the send (ret=-2\n// \"prepare failed\"), it fails fast instead of retrying: live testing showed the\n// penalty is escalated by every send attempt made while it is active, so retrying\n// (e.g. the old 3×500ms loop plus the extra notice send) only prolongs the outage.\nfunc (p *Platform) sendChunk(ctx context.Context, rc *replyContext, chunk string) error {\n\tclientID := \"cc-\" + randomHex(6)\n\terr := p.api.sendText(ctx, rc.peerUserID, chunk, rc.contextToken, clientID)\n\tif err == nil {\n\t\treturn nil\n\t}\n\tif isSendThrottled(err) {\n\t\treturn fmt.Errorf(\"weixin: sendMessage throttled by ilink (ret=-2); \"+\n\t\t\t\"the bot is rate-limited and sending during the penalty escalates it, retry the message later: %w\", err)\n\t}\n\treturn err\n}\n\nfunc truncatePreview(s string, max int) string {\n\tif len(s) <= max {\n\t\treturn s\n\t}\n\treturn s[:max] + \"...\"\n}\n\nfunc splitUTF8(s string, maxRunes int) []string {\n\tif maxRunes <= 0 || utf8.RuneCountInString(s) <= maxRunes {\n\t\treturn []string{s}\n\t}\n\tvar out []string\n\trunes := []rune(s)","sourceCodeStart":858,"sourceCodeEnd":894,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/weixin/weixin.go#L858-L894","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Stop sending immediately and wait out the penalty window before retrying","Enforce/lower the push quota (checkSendQuota) so ret=-2 is never reached","Consolidate notifications into fewer, larger messages per window","Back off exponentially in any retry loop instead of retrying promptly"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// avoid triggering ret=-2: keep pushes under the quota window\nif pushesInWindow >= 5 {\n    deferSendUntilWindowEnd(msg)\n    return nil\n}","typeGuard":null,"tryCatchPattern":"if err := p.Send(ctx, rc, msg); err != nil && strings.Contains(err.Error(), \"throttled by ilink\") {\n    log.Warn(\"ilink penalty active; backing off long\", \"err\", err)\n    time.Sleep(penaltyBackoff) // minutes, not seconds\n    return err // do NOT hot-retry; escalate the throttle\n}","preventionTips":["Never retry immediately on ret=-2 — waiting escalates nothing, hot retries escalate the penalty","Cap proactive pushes at 5-6 per window","Use Reply instead of Send where possible","Add long exponential backoff (minutes) to any retry path"],"tags":["go","rate-limit","weixin","throttling"],"backgroundTag":"rate-limit-exceeded","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}