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

  1. Look up the printed errcode in WeCom's error-code documentation to identify the exact business cause.
  2. For 40014/42001, invalidate the cached token (getAccessToken's tokenCache) and verify corpid/corpsecret; the next getAccessToken call will mint a fresh token.
  3. For 81013, validate the touser list: check userids exist and are within the app's visible range.
  4. For 40058, confirm agentid in config.toml matches the WeCom app and that the text payload shape is correct.
  5. 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

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


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