{"record":{"id":"7ba2ef01cdddd638","repo":"chenhg5/cc-connect","slug":"dingtalk-get-access-token-for-proactive-send-w","errorCode":null,"errorMessage":"dingtalk: get access token for proactive send: %w","messagePattern":"dingtalk: get access token for proactive send: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"platform/dingtalk/dingtalk.go","lineNumber":1646,"sourceCode":"\tif len(parts) > 2 {\n\t\tsenderStaffId = parts[2]\n\t}\n\n\treturn replyContext{\n\t\tconversationId: conversationId,\n\t\tsenderStaffId:  senderStaffId,\n\t\tisGroup:        convType == \"g\",\n\t\tproactive:      true,\n\t}, nil\n}\n\n// sendProactiveMessage sends a message using the DingTalk group/direct message API\n// instead of the temporary sessionWebhook. This enables cc-connect send, cron,\n// webhook, and other proactive messaging features.\nfunc (p *Platform) sendProactiveMessage(ctx context.Context, rc replyContext, content string) error {\n\ttoken, err := p.getAccessToken()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"dingtalk: get access token for proactive send: %w\", err)\n\t}\n\n\tcontent = preprocessDingTalkMarkdown(content)\n\n\tvar apiURL string\n\tvar requestBody map[string]any\n\n\tif rc.isGroup && rc.conversationId != \"\" {\n\t\t// Group message via /v1.0/robot/groupMessages/send\n\t\tapiURL = \"https://api.dingtalk.com/v1.0/robot/groupMessages/send\"\n\t\tmsgParam, _ := json.Marshal(map[string]string{\"text\": content})\n\t\trequestBody = map[string]any{\n\t\t\t\"robotCode\":          p.robotCode,\n\t\t\t\"openConversationId\": rc.conversationId,\n\t\t\t\"msgKey\":             \"sampleMarkdown\",\n\t\t\t\"msgParam\":           string(msgParam),\n\t\t}\n\t} else if rc.senderStaffId != \"\" {","sourceCodeStart":1628,"sourceCodeEnd":1664,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/dingtalk/dingtalk.go#L1628-L1664","documentation":"sendProactiveMessage — used by cc-connect send, cron, webhook and other proactive features — first obtains a DingTalk access token, and this error wraps any failure from getAccessToken. Unlike passive replies (which reuse the sessionWebhook), proactive sending requires the group/direct message API, which requires a valid app access_token. Without it, no proactive message can be sent.","triggerScenarios":"Any proactive send (CLI send command, cron job, webhook trigger) when getAccessToken fails: wrong appKey/appSecret in config, DingTalk token endpoint unreachable (network/DNS), token endpoint returning an error payload, or cached token expired and refresh failed.","commonSituations":"appKey/appSecret typo or credentials from a different/removed app in config.toml; enterprise firewall blocking egress to oapi.dingtalk.com; DingTalk app disabled or permissions revoked in the developer console; clock skew invalidating token caching logic.","solutions":["Inspect the wrapped inner error (%w) — it distinguishes credential rejection vs. network failure vs. API error response.","Verify appKey and appSecret in config.toml match an active app in the DingTalk Open Platform developer console.","Test connectivity: curl the token endpoint from the host running cc-connect to rule out firewall/DNS/proxy issues.","Confirm the app has the group chat / bot message permissions (qyapi scoped) required for proactive sending.","Restart cc-connect after fixing credentials so cached state resets, then re-run the proactive send."],"exampleFix":"// before\ntoken, err := p.getAccessToken()\nif err != nil {\n    return fmt.Errorf(\"dingtalk: get access token for proactive send: %w\", err)\n}\n// after\ntoken, err := p.getAccessToken()\nif err != nil {\n    slog.Error(\"dingtalk: proactive send aborted, token acquisition failed\", \"err\", err)\n    return fmt.Errorf(\"dingtalk: get access token for proactive send: %w\", err)\n}","handlingStrategy":"retry","validationCode":"// before a proactive send, confirm credentials exist and endpoint is reachable\nif p.appKey == \"\" || p.appSecret == \"\" {\n    return fmt.Errorf(\"dingtalk appKey/appSecret missing in config\")\n}\nresp, err := http.Post(p.tokenURL(), \"application/json\", strings.NewReader(\"{}\"))\nif err != nil {\n    return fmt.Errorf(\"token endpoint unreachable: %w\", err)\n}\n_ = resp.Body.Close()","typeGuard":"func canProactivelySend(p *Platform) bool {\n    return p != nil && p.appKey != \"\" && p.appSecret != \"\"\n}","tryCatchPattern":"err := platform.SendProactive(ctx, sessionKey, text)\nif err != nil {\n    if strings.Contains(err.Error(), \"get access token for proactive send\") {\n        // wait and retry once; token issues are often transient or fixed by config\n        time.Sleep(5 * time.Second)\n        err = platform.SendProactive(ctx, sessionKey, text)\n    }\n    if err != nil {\n        slog.Error(\"proactive send failed\", \"err\", err)\n        return err\n    }\n}","preventionTips":["Verify appKey/appSecret in config.toml against the active DingTalk app.","Confirm egress to oapi.dingtalk.com is allowed by firewalls/proxies.","Keep required bot/group-message permissions enabled in the developer console.","Refresh tokens proactively before expiry; avoid reusing stale cached tokens.","Restart cc-connect after credential changes to clear cached auth state."],"tags":["authentication","token","proactive","dingtalk"],"backgroundTag":"oauth-token-exchange-failed","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"}