chenhg5/cc-connect · critical

dingtalk: get access token for proactive send: %w

Error message

dingtalk: get access token for proactive send: %w

What it means

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.

Source

Thrown at platform/dingtalk/dingtalk.go:1646

	if len(parts) > 2 {
		senderStaffId = parts[2]
	}

	return replyContext{
		conversationId: conversationId,
		senderStaffId:  senderStaffId,
		isGroup:        convType == "g",
		proactive:      true,
	}, nil
}

// sendProactiveMessage sends a message using the DingTalk group/direct message API
// instead of the temporary sessionWebhook. This enables cc-connect send, cron,
// webhook, and other proactive messaging features.
func (p *Platform) sendProactiveMessage(ctx context.Context, rc replyContext, content string) error {
	token, err := p.getAccessToken()
	if err != nil {
		return fmt.Errorf("dingtalk: get access token for proactive send: %w", err)
	}

	content = preprocessDingTalkMarkdown(content)

	var apiURL string
	var requestBody map[string]any

	if rc.isGroup && rc.conversationId != "" {
		// Group message via /v1.0/robot/groupMessages/send
		apiURL = "https://api.dingtalk.com/v1.0/robot/groupMessages/send"
		msgParam, _ := json.Marshal(map[string]string{"text": content})
		requestBody = map[string]any{
			"robotCode":          p.robotCode,
			"openConversationId": rc.conversationId,
			"msgKey":             "sampleMarkdown",
			"msgParam":           string(msgParam),
		}
	} else if rc.senderStaffId != "" {

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Inspect the wrapped inner error (%w) — it distinguishes credential rejection vs. network failure vs. API error response.
  2. Verify appKey and appSecret in config.toml match an active app in the DingTalk Open Platform developer console.
  3. Test connectivity: curl the token endpoint from the host running cc-connect to rule out firewall/DNS/proxy issues.
  4. Confirm the app has the group chat / bot message permissions (qyapi scoped) required for proactive sending.
  5. Restart cc-connect after fixing credentials so cached state resets, then re-run the proactive send.

Example fix

// before
token, err := p.getAccessToken()
if err != nil {
    return fmt.Errorf("dingtalk: get access token for proactive send: %w", err)
}
// after
token, err := p.getAccessToken()
if err != nil {
    slog.Error("dingtalk: proactive send aborted, token acquisition failed", "err", err)
    return fmt.Errorf("dingtalk: get access token for proactive send: %w", err)
}
Defensive patterns

Strategy: retry

Validate before calling

// before a proactive send, confirm credentials exist and endpoint is reachable
if p.appKey == "" || p.appSecret == "" {
    return fmt.Errorf("dingtalk appKey/appSecret missing in config")
}
resp, err := http.Post(p.tokenURL(), "application/json", strings.NewReader("{}"))
if err != nil {
    return fmt.Errorf("token endpoint unreachable: %w", err)
}
_ = resp.Body.Close()

Type guard

func canProactivelySend(p *Platform) bool {
    return p != nil && p.appKey != "" && p.appSecret != ""
}

Try / catch

err := platform.SendProactive(ctx, sessionKey, text)
if err != nil {
    if strings.Contains(err.Error(), "get access token for proactive send") {
        // wait and retry once; token issues are often transient or fixed by config
        time.Sleep(5 * time.Second)
        err = platform.SendProactive(ctx, sessionKey, text)
    }
    if err != nil {
        slog.Error("proactive send failed", "err", err)
        return err
    }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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