{"record":{"id":"b74da82114faaaa5","repo":"chenhg5/cc-connect","slug":"dingtalk-empty-conversationid-in-session-key-q","errorCode":null,"errorMessage":"dingtalk: empty conversationId in session key: %q","messagePattern":"dingtalk: empty conversationId in session key: %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"platform/dingtalk/dingtalk.go","lineNumber":1624,"sourceCode":"\tif !strings.HasPrefix(sessionKey, \"dingtalk:\") {\n\t\treturn nil, fmt.Errorf(\"dingtalk: not a dingtalk session key: %q\", sessionKey)\n\t}\n\n\tstripped := strings.TrimPrefix(sessionKey, \"dingtalk:\")\n\tparts := strings.SplitN(stripped, \":\", 3)\n\n\tif len(parts) < 2 {\n\t\treturn nil, fmt.Errorf(\"dingtalk: invalid session key format: %q\", sessionKey)\n\t}\n\n\tconvType := parts[0]\n\tif convType != \"g\" && convType != \"d\" {\n\t\treturn nil, fmt.Errorf(\"dingtalk: invalid conversation type %q in session key: %q\", convType, sessionKey)\n\t}\n\n\tconversationId := parts[1]\n\tif conversationId == \"\" {\n\t\treturn nil, fmt.Errorf(\"dingtalk: empty conversationId in session key: %q\", sessionKey)\n\t}\n\n\tvar senderStaffId string\n\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.","sourceCodeStart":1606,"sourceCodeEnd":1642,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/dingtalk/dingtalk.go#L1606-L1642","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Verify the stored key actually contains the DingTalk conversation id (openConversationId / chatId) between the colons.","Re-derive the key from a live message to capture the real conversationId.","Check config for the cc-connect send/cron/webhook target — fill in the actual conversation id value.","Inspect the platform code path that first builds the key (message receive handler) to see why conversationId was empty in the original event."],"exampleFix":"// before\nsessionKey := fmt.Sprintf(\"dingtalk:%s:%s\", convType, conversationId) // conversationId == \"\"\n// after\nif conversationId == \"\" {\n    return fmt.Errorf(\"cannot build dingtalk session key: empty conversationId\")\n}\nsessionKey := fmt.Sprintf(\"dingtalk:%s:%s\", convType, conversationId)","handlingStrategy":"validation","validationCode":"func hasConversationId(key string) bool {\n    rest := strings.TrimPrefix(key, \"dingtalk:\")\n    parts := strings.SplitN(rest, \":\", 3)\n    return len(parts) >= 2 && parts[1] != \"\"\n}\n// only reconstruct when a conversation id is present\nif !hasConversationId(sessionKey) {\n    return fmt.Errorf(\"session key missing conversationId\")\n}","typeGuard":null,"tryCatchPattern":"ctx, err := platform.ReconstructReplyCtx(sessionKey)\nif err != nil {\n    if strings.Contains(err.Error(), \"empty conversationId\") {\n        slog.Error(\"dingtalk key has no conversationId; refetch from a live message or config\", \"key\", sessionKey)\n        return errMissingConversationID\n    }\n    return err\n}","preventionTips":["Always populate conversationId (openConversationId/chatId) when building keys.","Fill in placeholders in send/cron/webhook configs before deployment.","Reject empty conversation ids at message-receive time so bad keys are never persisted.","Sanity-check keys after any config templating/interpolation step."],"tags":["session-key","validation","dingtalk"],"backgroundTag":"empty-required-field","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"}