]> git.ipfire.org Git - thirdparty/rspamd.git/commitdiff
[Fix] Use class_name as Redis label for multiclass Bayes without class_labels config
authorVsevolod Stakhov <vsevolod@rspamd.com>
Fri, 13 Feb 2026 10:58:25 +0000 (10:58 +0000)
committerVsevolod Stakhov <vsevolod@rspamd.com>
Fri, 13 Feb 2026 10:58:25 +0000 (10:58 +0000)
get_class_label() fell through to the legacy "S"/"H" fallback when
class_labels hash table was NULL, even when an explicit class_name
(e.g. "cold_marketing") was set on the statfile. Since all non-binary
classes have is_spam=FALSE, every class mapped to "H", causing:
- All tokens stored under the same Redis hash field "H"
- All learn counters going to learns_ham
- Classification returning identical data for all classes
- Mempool runtime caching collisions (all saved under key _H)

Fix: check class_name with !is_spam_converted before the S/H fallback
so explicit multiclass configs use their actual class name as the Redis
label, while legacy binary configs (auto-converted from spam=true/false)
still use "S"/"H".

Also fix controller learn log to show actual class name instead of
always "spam"/"ham".

src/controller.c
src/libstat/backends/redis_backend.cxx

index a74e783aaecc2a2df5bd1a827d3608038188769d..ebd309798ba18a1cde5c0ebfb64d7b77e0d2827b 100644 (file)
@@ -2011,9 +2011,10 @@ rspamd_controller_learn_fin_task(void *ud)
 
        if (RSPAMD_TASK_IS_PROCESSED(task)) {
                /* Successful learn */
+               const char *learn_class = rspamd_task_get_autolearn_class(task);
                msg_info_task("<%s> learned message as %s: %s",
                                          rspamd_inet_address_to_string(session->from_addr),
-                                         session->is_spam ? "spam" : "ham",
+                                         learn_class ? learn_class : (session->is_spam ? "spam" : "ham"),
                                          MESSAGE_FIELD_CHECK(task, message_id));
                rspamd_controller_send_string(conn_ent, "{\"success\":true}");
                return TRUE;
@@ -2039,9 +2040,10 @@ rspamd_controller_learn_fin_task(void *ud)
                                                                                 task->err->message);
                }
                else {
+                       const char *learn_class = rspamd_task_get_autolearn_class(task);
                        msg_info_task("<%s> learned message as %s: %s",
                                                  rspamd_inet_address_to_string(session->from_addr),
-                                                 session->is_spam ? "spam" : "ham",
+                                                 learn_class ? learn_class : (session->is_spam ? "spam" : "ham"),
                                                  MESSAGE_FIELD_CHECK(task, message_id));
                        rspamd_controller_send_string(conn_ent, "{\"success\":true}");
                }
index 2acaf9db0d2f3cd25eb4295477173d2841226de9..8f93b61db6eed3a4211f7aad26da003f637de92d 100644 (file)
@@ -205,6 +205,11 @@ get_class_label(struct rspamd_statfile_config *stcf)
                return stcf->class_name;
        }
 
+       /* For multiclass without explicit label mapping, use class_name directly */
+       if (stcf->class_name && !stcf->is_spam_converted) {
+               return stcf->class_name;
+       }
+
        /* Fallback to legacy binary classification */
        return stcf->is_spam ? "S" : "H";
 }