{"record":{"id":"7e8a6b951cddbdc2","repo":"chenhg5/cc-connect","slug":"dingtalk-invalid-reply-context-type-t","errorCode":null,"errorMessage":"dingtalk: invalid reply context type %T","messagePattern":"dingtalk: invalid reply context type %T","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"platform/dingtalk/dingtalk.go","lineNumber":801,"sourceCode":"\tif expiry <= 0 {\n\t\tslog.Warn(\"dingtalk: missing/invalid expireIn in token response, defaulting to 7200s\", \"got\", tokenResp.ExpireIn)\n\t\texpiry = 7200\n\t}\n\tif expiry > 300 {\n\t\texpiry -= 300 // 5 minute buffer\n\t}\n\tp.tokenExpiry = time.Now().Add(time.Duration(expiry) * time.Second)\n\n\tslog.Debug(\"dingtalk: access token refreshed\", \"expires_at\", p.tokenExpiry)\n\treturn p.accessToken, nil\n}\n\n// ReplyWithAt sends a reply with @mention support. Uses text msgtype (not markdown)\n// because only text type supports highlighted/blue @mentions in DingTalk.\nfunc (p *Platform) ReplyWithAt(ctx context.Context, rctx any, content string, atUsers []string, atAll bool) error {\n\trc, ok := rctx.(replyContext)\n\tif !ok {\n\t\treturn fmt.Errorf(\"dingtalk: invalid reply context type %T\", rctx)\n\t}\n\tif rc.proactive || rc.sessionWebhook == \"\" {\n\t\treturn p.sendProactiveMessage(ctx, rc, content)\n\t}\n\n\tpayload := map[string]any{\n\t\t\"msgtype\": \"text\",\n\t\t\"text\":    map[string]string{\"content\": content},\n\t}\n\tif len(atUsers) > 0 || atAll {\n\t\tpayload[\"at\"] = map[string]any{\n\t\t\t\"atUserIds\": atUsers,\n\t\t\t\"isAtAll\":   atAll,\n\t\t}\n\t}\n\tbody, err := json.Marshal(payload)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"dingtalk: marshal reply: %w\", err)","sourceCodeStart":783,"sourceCodeEnd":819,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/dingtalk/dingtalk.go#L783-L819","documentation":"ReplyWithAt expects its rctx parameter to be the platform-internal replyContext type (the value the platform itself produced when receiving a message). If the caller passes anything else — e.g. a reply context from another platform, a raw string, or a constructed struct of the wrong type — the type assertion fails and this error reports the actual Go type received.","triggerScenarios":"Calling Platform.ReplyWithAt directly with a context obtained from a different platform adapter, from an older cc-connect version with a different internal replyContext layout, or a hand-constructed value rather than one produced by the DingTalk adapter's message receive path.","commonSituations":"Custom integrations that cache reply contexts across platforms, mixing adapters in one process and passing a telegram/discord reply context to the DingTalk platform, or holding a stale context after an engine restart.","solutions":["Only pass replyContext values originally produced by the DingTalk platform (from its receive/reply flow), never from other platform adapters.","Check the reported %T in the message — if it's another platform's type, route the reply through that platform instead.","Update cc-connect if contexts are persisted/serialized across versions; internal types are not stable across releases.","If constructing contexts manually, use the exported APIs the platform provides instead of forging internal types.","Verify via core.Engine Reply APIs rather than calling platform internals directly."],"exampleFix":"// before: passing a foreign context\nerr := dingtalkPlat.ReplyWithAt(ctx, someTelegramCtx, \"hi\", ats, false)\n\n// after: reply through the originating platform/engine\nerr := originatingPlatform.Reply(ctx, replyCtx, \"hi\")","handlingStrategy":"type-guard","validationCode":"if rc, ok := rctx.(platform.replyContext); !ok {\n    return fmt.Errorf(\"reply context of type %T is not a dingtalk replyContext\", rctx)\n}","typeGuard":"func isDingTalkReplyContext(v any) bool {\n    _, ok := v.(replyContext)\n    return ok\n}","tryCatchPattern":"if err := p.ReplyWithAt(ctx, rctx, text, ats, false); err != nil {\n    if strings.Contains(err.Error(), \"invalid reply context type\") {\n        // fall back to the engine-level Reply which routes correctly\n        return engine.Reply(ctx, rctx, text)\n    }\n    return err\n}","preventionTips":["Only pass reply contexts produced by the same platform adapter.","Don't persist reply contexts across cc-connect upgrades — internal types change.","Prefer core.Engine reply APIs over calling platform internals directly.","Read the %T value in the error to immediately identify the mismatched type's origin."],"tags":["type-safety","dingtalk","api-misuse","go"],"backgroundTag":"type-mismatch","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"}