{"record":{"id":"45eeabea9d71c101","repo":"chenhg5/cc-connect","slug":"dingtalk-not-a-dingtalk-session-key-q","errorCode":null,"errorMessage":"dingtalk: not a dingtalk session key: %q","messagePattern":"dingtalk: not a dingtalk session key: %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"platform/dingtalk/dingtalk.go","lineNumber":1607,"sourceCode":"\nfunc normalizeQuotedMessageText(s string) string {\n\ttext := strings.TrimSpace(s)\n\tif text == \"\" {\n\t\treturn \"\"\n\t}\n\trunes := []rune(text)\n\tif len(runes) <= maxQuotedMessageRunes {\n\t\treturn text\n\t}\n\treturn string(runes[:maxQuotedMessageRunes]) + \"...\"\n}\n\n// ReconstructReplyCtx implements core.ReplyContextReconstructor.\n// Session key format: \"dingtalk:{convType}:{conversationId}:{senderStaffId}\" or \"dingtalk:{convType}:{conversationId}\"\n// where convType is \"g\" (group) or \"d\" (direct/1:1).\nfunc (p *Platform) ReconstructReplyCtx(sessionKey string) (any, error) {\n\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}","sourceCodeStart":1589,"sourceCodeEnd":1625,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/dingtalk/dingtalk.go#L1589-L1625","documentation":"ReconstructReplyCtx (the core.ReplyContextReconstructor implementation) received a session key that does not start with the 'dingtalk:' prefix, so it cannot belong to this platform. Each platform owns its own session-key namespace; passing a key from another platform (e.g. 'telegram:...') to DingTalk's reconstructor is invalid. This is a defensive namespace check.","triggerScenarios":"Calling ReconstructReplyCtx with a session key generated by a different platform adapter, or with an arbitrary/corrupted string (empty, renamed, or hand-built key lacking the 'dingtalk:' prefix).","commonSituations":"Storing session keys from mixed platforms in one database and replaying them to the wrong adapter; config pointing a cron/webhook at the wrong platform's session key; manual key construction after an upgrade changed the format.","solutions":["Verify the session key was produced by the DingTalk platform (starts with 'dingtalk:').","Check the /sessions listing or persistence layer for the correct platform-scoped key.","If migrating keys across platforms, reconstruct via the correct platform's ReconstructReplyCtx, not DingTalk's.","For cc-connect send/cron/webhook commands, ensure the --session or config value references a dingtalk:* key when the target platform is dingtalk."],"exampleFix":"// before\nrc, err := platform.ReconstructReplyCtx(sessionKey)\n// after\nif !strings.HasPrefix(sessionKey, \"dingtalk:\") {\n    return fmt.Errorf(\"refusing to reconstruct: %q is not a dingtalk session key\", sessionKey)\n}\nrc, err := platform.ReconstructReplyCtx(sessionKey)","handlingStrategy":"validation","validationCode":"func reconstructIfDingtalk(platform *Platform, sessionKey string) (any, error) {\n    if !strings.HasPrefix(sessionKey, \"dingtalk:\") {\n        return nil, fmt.Errorf(\"skip: key %q is not a dingtalk key\", sessionKey)\n    }\n    return platform.ReconstructReplyCtx(sessionKey)\n}","typeGuard":"func isDingtalkSessionKey(key string) bool {\n    return strings.HasPrefix(key, \"dingtalk:\")\n}","tryCatchPattern":"ctx, err := platform.ReconstructReplyCtx(sessionKey)\nif err != nil {\n    if strings.Contains(err.Error(), \"not a dingtalk session key\") {\n        // route to the platform that owns this key instead\n        slog.Warn(\"wrong platform for key; lookup owning platform\", \"key\", sessionKey)\n        return handleWrongPlatform(sessionKey)\n    }\n    return err\n}","preventionTips":["Namespace stored session keys by platform and route reconstruction by prefix.","Never hand-build session keys; always capture them from the adapter.","When exporting/importing sessions across platforms, filter keys by prefix first.","Validate platform config (send/cron targets) references keys from the configured platform."],"tags":["session-key","validation","dingtalk"],"backgroundTag":"invalid-identifier-format","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}