{"record":{"id":"737d9ab793bc0903","repo":"chenhg5/cc-connect","slug":"dingtalk-proactive-send-failed-status-d-body","errorCode":null,"errorMessage":"dingtalk: proactive send failed: status=%d, body=%s","messagePattern":"dingtalk: proactive send failed: status=(.+?), body=(.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"platform/dingtalk/dingtalk.go","lineNumber":1698,"sourceCode":"\t\treturn fmt.Errorf(\"dingtalk: marshal proactive message: %w\", err)\n\t}\n\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, apiURL, bytes.NewReader(body))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"dingtalk: create proactive request: %w\", err)\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\treq.Header.Set(\"x-acs-dingtalk-access-token\", token)\n\n\tresp, err := p.httpClient.Do(req)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"dingtalk: proactive send request: %w\", err)\n\t}\n\tdefer func() { _ = resp.Body.Close() }()\n\n\trespBody, _ := io.ReadAll(resp.Body)\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn fmt.Errorf(\"dingtalk: proactive send failed: status=%d, body=%s\", resp.StatusCode, string(respBody))\n\t}\n\n\tslog.Debug(\"dingtalk: proactive message sent\", \"api\", apiURL, \"status\", resp.StatusCode)\n\treturn nil\n}\n\nvar atUserIDRegexp = regexp.MustCompile(`@(\\d{4,})`)\n\n// extractAtUserIds extracts @userId patterns from content for DingTalk's atUserIds field.\n// Matches @ followed by numeric DingTalk user IDs (e.g. @194252073827812352).\nfunc extractAtUserIds(content string) []string {\n\tmatches := atUserIDRegexp.FindAllStringSubmatch(content, -1)\n\tseen := make(map[string]bool)\n\tvar ids []string\n\tfor _, m := range matches {\n\t\tif len(m) > 1 && !seen[m[1]] {\n\t\t\tseen[m[1]] = true\n\t\t\tids = append(ids, m[1])","sourceCodeStart":1680,"sourceCodeEnd":1716,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/dingtalk/dingtalk.go#L1680-L1716","documentation":"The DingTalk proactive-send API responded with a non-200 HTTP status. The error includes the numeric status and the raw response body so the DingTalk-side failure reason (invalid token, invalid robot code, bad conversationId, rate limit) can be diagnosed. This is a server-rejected request, not a transport failure.","triggerScenarios":"The POST to the DingTalk proactive-send endpoint completed but resp.StatusCode != 200 — e.g. expired/invalid access token (invalidAuthentication), wrong robotCode, unknown conversationId/staffId, or exceeded QPS limits.","commonSituations":"Access token cached past its 2-hour expiry; robot not added to the target group so the conversationId is unauthorized; typo'd staffId; hitting DingTalk API rate limits during bulk notifications.","solutions":["Read the status and body in the error message; DingTalk bodies contain errcode/errmsg pinpointing the cause.","If status is 401/invalid token: refresh the access token (it expires ~2h) and retry; verify appKey/appSecret used to get the token.","Verify robotCode matches the app's robot, and that the bot is a member of the target group for group sends.","Check the conversationId/staffId values are current; re-capture them from a live inbound message.","If status is 429 or body mentions limit, slow down and add backoff retry."],"exampleFix":"// before\ntoken := p.tokenCache.Get() // may be hours old\np.sendProactive(ctx, token, requestBody)\n// after\ntoken, err := p.getAccessToken(ctx) // refreshes when near expiry\nif err != nil {\n    return fmt.Errorf(\"dingtalk: refresh token: %w\", err)\n}\np.sendProactive(ctx, token, requestBody)","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"err := p.Send(ctx, rc, msg)\nif err != nil {\n    var apiErr struct{ Status int; Body string }\n    if strings.Contains(err.Error(), \"proactive send failed: status=\") {\n        slog.Error(\"dingtalk rejected proactive send\", \"err\", err) // body has errcode/errmsg\n        if strings.Contains(err.Error(), \"invalidAuthentication\") {\n            // refresh token and retry once\n        }\n    }\n}","preventionTips":["Refresh the access token proactively before its ~2h expiry","Verify the bot is a member of the target group for group sends","Validate robotCode, conversationId, and staffId against a live inbound message","Handle 429 with backoff and reduce send rate"],"tags":["dingtalk","http-status","api-error","proactive-send","authentication"],"backgroundTag":"http-error-response","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"}