chenhg5/cc-connect · error
wecom: send failed: %d %s
Error message
wecom: send failed: %d %s
What it means
This error is returned by sendText when the WeCom /cgi-bin/message/send API responded successfully at the HTTP level but reported a business failure via a non-zero errcode in its JSON response. The message carries the numeric errcode and server errmsg, e.g. 'wecom: send failed: 40058 invalid parameter'. Matching the errcode against WeCom's documented error table is the required first step for diagnosis.
Source
Thrown at platform/wecom/wecom.go:652
apiURL := p.wecomAPIURL("/cgi-bin/message/send", url.Values{
"access_token": []string{accessToken},
})
resp, err := p.apiClient.Post(apiURL, "application/json", strings.NewReader(string(body)))
if err != nil {
return fmt.Errorf("wecom: send message: %w", err)
}
defer resp.Body.Close()
var result struct {
ErrCode int `json:"errcode"`
ErrMsg string `json:"errmsg"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return fmt.Errorf("wecom: decode send response: %w", err)
}
if result.ErrCode != 0 {
return fmt.Errorf("wecom: send failed: %d %s", result.ErrCode, result.ErrMsg)
}
return nil
}
func (p *Platform) getAccessToken() (string, error) {
p.tokenCache.mu.Lock()
defer p.tokenCache.mu.Unlock()
if p.tokenCache.token != "" && time.Now().Before(p.tokenCache.expiresAt) {
return p.tokenCache.token, nil
}
apiURL := p.wecomAPIURL("/cgi-bin/gettoken", url.Values{
"corpid": []string{p.corpID},
"corpsecret": []string{p.corpSecret},
})
resp, err := p.apiClient.Get(apiURL)View on GitHub (pinned to 4000b2338a)
Solutions
- Look up the printed errcode in WeCom's error-code documentation to identify the exact business cause.
- For 40014/42001, invalidate the cached token (getAccessToken's tokenCache) and verify corpid/corpsecret; the next getAccessToken call will mint a fresh token.
- For 81013, validate the touser list: check userids exist and are within the app's visible range.
- For 40058, confirm agentid in config.toml matches the WeCom app and that the text payload shape is correct.
- For 45009, throttle sends with backoff; batch or queue messages instead of firing them concurrently.
Example fix
// before
if result.ErrCode != 0 {
return fmt.Errorf("wecom: send failed: %d %s", result.ErrCode, result.ErrMsg)
}
// after — auto-recover on token expiry
if result.ErrCode == 40014 || result.ErrCode == 42001 {
p.tokenCache.mu.Lock()
p.tokenCache.token = ""
p.tokenCache.expiry = time.Time{}
p.tokenCache.mu.Unlock()
return fmt.Errorf("wecom: send failed (token expired, cache cleared, will retry): %d %s", result.ErrCode, result.ErrMsg)
}
if result.ErrCode != 0 {
return fmt.Errorf("wecom: send failed: %d %s", result.ErrCode, result.ErrMsg)
} Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-flight checks before sendText:
if accessToken == "" || agentID == 0 || toUser == "" || strings.TrimSpace(text) == "" {
return fmt.Errorf("wecom: pre-send validation failed: missing token/agentid/user/content")
}
if len([]rune(text)) > 2048 { // WeCom text content practical limit
return fmt.Errorf("wecom: text content too long: %d runes", len([]rune(text)))
} Type guard
func wecomSendErrCode(err error) (int, bool) {
if err == nil {
return 0, false
}
var code int
if _, scanErr := fmt.Sscanf(err.Error(), "wecom: send failed: %d", &code); scanErr == nil {
return code, true
}
return 0, false
} Try / catch
if err := p.Send(msg); err != nil {
if code, ok := wecomSendErrCode(err); ok {
switch code {
case 40014, 42001: // token expired: clear cache and retry once
p.invalidateTokenCache()
case 81013: // bad touser: fix recipient config
slog.Warn("wecom: unknown touser", "err", err)
case 45009: // rate limited: back off
time.Sleep(rateBackoff)
default:
slog.Error("wecom: send text rejected", "errcode", code, "errmsg", err)
}
}
} Prevention
- Parse and route on errcode: auth codes (40014/42001), recipient codes (81013), and rate-limit codes (45009) need different handling.
- Verify agentid, corpid, and corpsecret in config.toml against the WeCom admin console at startup (doctor check).
- Auto-refresh the token cache when an auth errcode appears instead of requiring a restart.
- Validate target userids and keep text payloads within WeCom size limits; queue fan-out sends to respect rate limits.
When it happens
Trigger: sendText posts a payload of the form {"touser":..., "msgtype":"text", "agentid":..., "text":{"content":...}} and the response contains result.ErrCode != 0. Frequent codes: 40014/42001 invalid/expired access_token; 81013 unknown touser; 40058 bad agentid or parameter format; 45009 rate limit; 81004 disabled app.
Common situations: 1) stale cached access_token after a corpsecret rotation; 2) agentid mismatch between config.toml and the WeCom admin console; 3) target userid not in the app's visible scope or deleted; 4) empty/oversized text content; 5) exceeding API rate limits when the bot fans out to many chats.
Related errors
- wecom: send markdown failed: %d %s
- wecom-ws: ack error: errcode=%d errmsg=%s
- wecom-ws: ack timeout
- usage endpoint returned status %d: %s
- iflow API request failed
AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06).
Data as JSON: /api/errors/a0f3c5aebca6682a.
Report an issue: GitHub.