{"record":{"id":"a7f4c7ac8704c867","repo":"chenhg5/cc-connect","slug":"wecom-send-markdown-failed-d-s","errorCode":null,"errorMessage":"wecom: send markdown failed: %d %s","messagePattern":"wecom: send markdown failed: (.+?) (.+?)","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"platform/wecom/wecom.go","lineNumber":619,"sourceCode":"\tapiURL := p.wecomAPIURL(\"/cgi-bin/message/send\", url.Values{\n\t\t\"access_token\": []string{accessToken},\n\t})\n\n\tresp, err := p.apiClient.Post(apiURL, \"application/json\", strings.NewReader(string(body)))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"wecom: send markdown: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tvar result struct {\n\t\tErrCode int    `json:\"errcode\"`\n\t\tErrMsg  string `json:\"errmsg\"`\n\t}\n\tif err := json.NewDecoder(resp.Body).Decode(&result); err != nil {\n\t\treturn fmt.Errorf(\"wecom: decode send response: %w\", err)\n\t}\n\tif result.ErrCode != 0 {\n\t\treturn fmt.Errorf(\"wecom: send markdown failed: %d %s\", result.ErrCode, result.ErrMsg)\n\t}\n\treturn nil\n}\n\nfunc (p *Platform) sendText(accessToken, toUser, text string) error {\n\tpayload := map[string]any{\n\t\t\"touser\":  toUser,\n\t\t\"msgtype\": \"text\",\n\t\t\"agentid\": p.agentID,\n\t\t\"text\":    map[string]string{\"content\": text},\n\t\t\"safe\":    0,\n\t}\n\n\tbody, _ := json.Marshal(payload)\n\tapiURL := p.wecomAPIURL(\"/cgi-bin/message/send\", url.Values{\n\t\t\"access_token\": []string{accessToken},\n\t})\n","sourceCodeStart":601,"sourceCodeEnd":637,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/wecom/wecom.go#L601-L637","documentation":"This error is returned by sendMarkdown when the WeCom /cgi-bin/message/send API accepted the HTTP request but rejected the business request: the JSON response carried a non-zero errcode. The error message embeds the numeric errcode and the server-provided errmsg, e.g. 'wecom: send markdown failed: 40008 invalid message type'. The errcode is the authoritative diagnosis — it distinguishes auth problems (40014 invalid access_token), bad targets (43004, 81013 invalid user), rate limits (45009), and payload problems (40008).","triggerScenarios":"Reply -> sendMarkdown posts a markdown payload whose JSON response contains result.ErrCode != 0. Typical errcodes: 40014/42001 expired or invalid access_token; 40008 markdown content invalid; 81013 invalid userid list (toUser); 45009 API rate limit exceeded; 40058 invalid parameter format.","commonSituations":"1) access_token cache expired mid-flight or corpsecret rotated; 2) messaging a user whose account was disabled/deleted or whose userid is misspelled in config; 3) markdown body exceeding WeCom's size limits or containing unsupported constructs; 4) hitting qyapi rate limits during bot storms; 5) app visibility — the target user is not in the app's visible scope.","solutions":["Read the errcode in the message and match it against WeCom's error-code table (https://developer.work.weixin.qq.com/document/path/90313) — each code has a documented cause.","For 40014/42001, force a token refresh: clear the token cache or wait for getAccessToken's expiry logic, and verify corpid/corpsecret in config.toml.","For 81013/43004, verify the target userid exists and is within the app's visible range; print the toUser value being sent.","For 45009, add backoff/rate limiting before retrying sends.","For 40008/40058, validate the markdown payload (length, characters) and reduce message size or simplify formatting."],"exampleFix":"// before\nerr := p.Reply(...) // sendMarkdown fails: wecom: send markdown failed: 40014 invalid credential\n// after — refresh token before send\ntok, err := p.getAccessToken()\nif err != nil {\n    return fmt.Errorf(\"wecom: reply: %w\", err)\n}\nif wecomErrCode(err) == 40014 || wecomErrCode(err) == 42001 { // parse from wrapped error or extend result handling\n    p.invalidateTokenCache()\n    tok, err = p.getAccessToken()\n    if err != nil {\n        return fmt.Errorf(\"wecom: refresh token: %w\", err)\n    }\n    return p.sendMarkdown(tok, toUser, md)\n}","handlingStrategy":"try-catch","validationCode":"// Validate prerequisites before sending markdown:\n// 1) token is fresh, 2) target user is set, 3) payload is non-empty and within size limits\nif accessToken == \"\" || toUser == \"\" || strings.TrimSpace(markdown) == \"\" {\n    return fmt.Errorf(\"wecom: pre-send validation failed: empty token/user/content\")\n}\nif len(markdown) > 4096 { // WeCom content limit guard\n    return fmt.Errorf(\"wecom: markdown too large: %d bytes\", len(markdown))\n}","typeGuard":"func wecomAPIErrCode(err error) (int, bool) {\n    if err == nil {\n        return 0, false\n    }\n    var code int\n    if _, scanErr := fmt.Sscanf(err.Error(), \"wecom: send markdown failed: %d\", &code); scanErr == nil {\n        return code, true\n    }\n    return 0, false\n}","tryCatchPattern":"if err := p.Reply(msg, md); err != nil {\n    if code, ok := wecomAPIErrCode(err); ok {\n        switch code {\n        case 40014, 42001:\n            p.invalidateTokenCache(); // retry once with fresh token\n        case 81013:\n            slog.Warn(\"wecom: invalid target userid\", \"toUser\", toUser)\n        case 45009:\n            time.Sleep(backoff); // rate limited: retry later\n        default:\n            slog.Error(\"wecom: send markdown rejected\", \"errcode\", code, \"errmsg\", err)\n        }\n    }\n}","preventionTips":["Monitor errcodes and alert on 40014/42001 (token) vs 81013 (user) — they have completely different remediations.","Keep corpid/corpsecret/agentid in config.toml verified against the WeCom admin console; rotate secrets with a token-cache flush.","Confirm every configured target userid is inside the app's visible scope before deployment.","Throttle outbound sends to stay under WeCom API rate limits."],"tags":["wecom","api","errcode","message-send"],"backgroundTag":"api-error-response","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}