From: Vsevolod Stakhov Date: Fri, 13 Feb 2026 10:58:25 +0000 (+0000) Subject: [Fix] Use class_name as Redis label for multiclass Bayes without class_labels config X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c7b5715a92072f2c1f6813ec533919af5142601c;p=thirdparty%2Frspamd.git [Fix] Use class_name as Redis label for multiclass Bayes without class_labels config 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". --- diff --git a/src/controller.c b/src/controller.c index a74e783aae..ebd309798b 100644 --- a/src/controller.c +++ b/src/controller.c @@ -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}"); } diff --git a/src/libstat/backends/redis_backend.cxx b/src/libstat/backends/redis_backend.cxx index 2acaf9db0d..8f93b61db6 100644 --- a/src/libstat/backends/redis_backend.cxx +++ b/src/libstat/backends/redis_backend.cxx @@ -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"; }