chenhg5/cc-connect · error

dingtalk: empty conversationId in session key: %q

Error message

dingtalk: empty conversationId in session key: %q

What it means

The session key's conversationId segment (the second colon-separated part) is an empty string. Even with a valid 'dingtalk:' prefix and a valid convType, a key without a concrete conversation id cannot be turned back into a reply context, so reconstruction fails. conversationId is mandatory in every DingTalk session key.

Source

Thrown at platform/dingtalk/dingtalk.go:1624

	if !strings.HasPrefix(sessionKey, "dingtalk:") {
		return nil, fmt.Errorf("dingtalk: not a dingtalk session key: %q", sessionKey)
	}

	stripped := strings.TrimPrefix(sessionKey, "dingtalk:")
	parts := strings.SplitN(stripped, ":", 3)

	if len(parts) < 2 {
		return nil, fmt.Errorf("dingtalk: invalid session key format: %q", sessionKey)
	}

	convType := parts[0]
	if convType != "g" && convType != "d" {
		return nil, fmt.Errorf("dingtalk: invalid conversation type %q in session key: %q", convType, sessionKey)
	}

	conversationId := parts[1]
	if conversationId == "" {
		return nil, fmt.Errorf("dingtalk: empty conversationId in session key: %q", sessionKey)
	}

	var senderStaffId string
	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.

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Verify the stored key actually contains the DingTalk conversation id (openConversationId / chatId) between the colons.
  2. Re-derive the key from a live message to capture the real conversationId.
  3. Check config for the cc-connect send/cron/webhook target — fill in the actual conversation id value.
  4. Inspect the platform code path that first builds the key (message receive handler) to see why conversationId was empty in the original event.

Example fix

// before
sessionKey := fmt.Sprintf("dingtalk:%s:%s", convType, conversationId) // conversationId == ""
// after
if conversationId == "" {
    return fmt.Errorf("cannot build dingtalk session key: empty conversationId")
}
sessionKey := fmt.Sprintf("dingtalk:%s:%s", convType, conversationId)
Defensive patterns

Strategy: validation

Validate before calling

func hasConversationId(key string) bool {
    rest := strings.TrimPrefix(key, "dingtalk:")
    parts := strings.SplitN(rest, ":", 3)
    return len(parts) >= 2 && parts[1] != ""
}
// only reconstruct when a conversation id is present
if !hasConversationId(sessionKey) {
    return fmt.Errorf("session key missing conversationId")
}

Try / catch

ctx, err := platform.ReconstructReplyCtx(sessionKey)
if err != nil {
    if strings.Contains(err.Error(), "empty conversationId") {
        slog.Error("dingtalk key has no conversationId; refetch from a live message or config", "key", sessionKey)
        return errMissingConversationID
    }
    return err
}

Prevention

When it happens

Trigger: Passing a key like 'dingtalk:g:' or 'dingtalk:d::staff123' where the conversationId slot is blank — typically from an incoming message whose cid field was empty/missing, or a key assembled from partial data.

Common situations: Webhook/cron config where the conversationId placeholder was never filled; DingTalk payloads with an unusual structure leaving conversationId unpopulated before the key was built; string-building bugs dropping the id.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


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