{"record":{"id":"9a78d7edd63e0d80","repo":"chenhg5/cc-connect","slug":"dingtalk-emotion-returned-success-false","errorCode":null,"errorMessage":"dingtalk: emotion returned success=false","messagePattern":"dingtalk: emotion returned success=false","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"platform/dingtalk/dingtalk.go","lineNumber":976,"sourceCode":"\n\tresp, err := p.httpClient.Do(req)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"dingtalk: emotion request: %w\", err)\n\t}\n\tdefer func() { _ = resp.Body.Close() }()\n\n\trespBody, _ := io.ReadAll(resp.Body)\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn fmt.Errorf(\"dingtalk: emotion returned status %d: %s\", resp.StatusCode, string(respBody))\n\t}\n\tif len(respBody) == 0 {\n\t\treturn nil\n\t}\n\tvar result struct {\n\t\tSuccess *bool `json:\"success\"`\n\t}\n\tif err := json.Unmarshal(respBody, &result); err == nil && result.Success != nil && !*result.Success {\n\t\treturn fmt.Errorf(\"dingtalk: emotion returned success=false\")\n\t}\n\treturn nil\n}\n\n// StartTyping adds a DingTalk emotion to the user's message while the agent is processing.\nfunc (p *Platform) StartTyping(ctx context.Context, rctx any) (stop func()) {\n\trc, ok := rctx.(replyContext)\n\tif !ok || p.reactionEmoji == \"\" || rc.messageID == \"\" || rc.conversationId == \"\" {\n\t\treturn func() {}\n\t}\n\tif err := p.sendEmotion(ctx, rc, p.reactionEmoji, false); err != nil {\n\t\tslog.Debug(\"dingtalk: add typing emotion failed\", \"error\", err)\n\t}\n\treturn func() {\n\t\tctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\n\t\tdefer cancel()\n\t\tif err := p.sendEmotion(ctx, rc, p.reactionEmoji, true); err != nil {\n\t\t\tslog.Debug(\"dingtalk: recall typing emotion failed\", \"error\", err)","sourceCodeStart":958,"sourceCodeEnd":994,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/dingtalk/dingtalk.go#L958-L994","documentation":"This error is returned by sendEmotion when DingTalk's emotion API returns HTTP 200 but the JSON body contains success=false, meaning DingTalk accepted the request at the transport layer but refused/refused-to-apply the emotion business operation. DingTalk reports some business failures (e.g. target message no longer exists, robot cannot reply to that message) inside a 200 envelope rather than an HTTP error status, so the library inspects the success field explicitly.","triggerScenarios":"POST to /v1.0/robot/emotion/reply or /robot/emotion/recall returns 200 with {\"success\":false} — typically the target message is too old or deleted, the robot lacks emotion-reply rights for that conversation, or the recall targeted an emotion that no longer exists.","commonSituations":"AddDoneReaction fired after the user's message was withdrawn; StartTyping racing message deletion; robot app not upgraded to support emotion reply in that org; DingTalk quirk returning success=false for unsupported conversation types.","solutions":["Treat as non-fatal if it's the typing indicator (StartTyping) — it is cosmetic and safe to ignore","Verify the robot app has emotion reply enabled for your org/conversation type in the DingTalk console","Check whether the target messageId still exists; don't react to withdrawn/expired messages","Capture the full response body (enable debug logging) to see any accompanying errcode","Update the DingTalk app/robot to the latest version if emotion features were recently added"],"exampleFix":"// before: unconditionally reacting to every inbound message\np.AddDoneReaction(ctx, msg)\n// after: skip if the message was withdrawn or is too old\nif msg.Withdrawn || time.Since(msg.Timestamp) > maxEmotionAge { return nil }\nreturn p.AddDoneReaction(ctx, msg)","handlingStrategy":"fallback","validationCode":"// Go: skip reaction if the target message is gone or too old\nif msg.Withdrawn || time.Since(msg.Timestamp) > 10*time.Minute {\n    return nil\n}","typeGuard":null,"tryCatchPattern":"if err := p.AddDoneReaction(ctx, msg); err != nil {\n    if strings.Contains(err.Error(), \"success=false\") {\n        // business refusal, not a bug — degrade gracefully\n        slog.Debug(\"dingtalk emotion not applied\", \"msg\", msg.ID)\n        return nil\n    }\n    return err\n}","preventionTips":["Don't react to messages older than the platform's retention window","Check message withdrawal state before reacting","Enable emotion reply for the robot app at the org level","Treat typing/reaction failures as cosmetic — never propagate them to the chat flow"],"tags":["dingtalk","api","business-logic"],"backgroundTag":"api-error-response","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"}