From 8cf7df867e7d18e2a4acd8c91d0cb670ea3c9a20 Mon Sep 17 00:00:00 2001 From: ruangraung Date: Sat, 20 Jun 2026 02:42:01 +0700 Subject: [PATCH] fix(plugins): silence raft check_fn log spam for users without raft CLI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The raft platform plugin's check_raft_requirements() logged a WARNING every time it returned False. Since check_fn is called on every load_gateway_config() (~every 10s during normal gateway operation), users who don't have the raft CLI installed get their logs flooded with no way to suppress it — hermes plugins disable doesn't work for bundled platform plugins, and platforms.raft.enabled: false doesn't gate the check_fn call. Fix: make check_raft_requirements() a silent predicate (return True/False only, no logging), matching the convention documented and used by other platform adapters (e.g. teams/adapter.py). The caller in gateway/platform_registry.py create_adapter() already emits its own warning when requirements aren't met and an adapter is actually requested — that's the correct place for a user-facing warning (fires once per connect attempt, not once per config load). Fixes #49234 --- plugins/platforms/raft/adapter.py | 14 +++- tests/plugins/test_raft_check_fn_silent.py | 75 ++++++++++++++++++++++ 2 files changed, 86 insertions(+), 3 deletions(-) create mode 100644 tests/plugins/test_raft_check_fn_silent.py diff --git a/plugins/platforms/raft/adapter.py b/plugins/platforms/raft/adapter.py index 5623cef0e5e..67e34b2a906 100644 --- a/plugins/platforms/raft/adapter.py +++ b/plugins/platforms/raft/adapter.py @@ -98,12 +98,20 @@ _RAFT_PROMPT_TURN_IDS: set[str] = set() def check_raft_requirements() -> bool: - """Check if Raft channel dependencies are available.""" + """Check if Raft channel dependencies are available. + + Intentionally silent on failure — this is a passive probe registered as + the platform's ``check_fn``. It is called on every + ``load_gateway_config()`` (message handling, display lookups, agent + turns), so logging here floods the logs for every user without the + ``raft`` CLI installed. The caller (``gateway/platform_registry.py`` + ``create_adapter()``) emits its own warning when requirements are not met + and an adapter is actually requested. This matches the convention used by + other platform adapters (e.g. ``teams/adapter.py``). + """ if not AIOHTTP_AVAILABLE: - logger.warning("[raft] aiohttp is not installed — install with: pip install aiohttp") return False if not shutil.which("raft"): - logger.warning("[raft] raft CLI not found in PATH — install from https://raft.build") return False return True diff --git a/tests/plugins/test_raft_check_fn_silent.py b/tests/plugins/test_raft_check_fn_silent.py new file mode 100644 index 00000000000..76a906a9c54 --- /dev/null +++ b/tests/plugins/test_raft_check_fn_silent.py @@ -0,0 +1,75 @@ +"""Regression tests for the raft platform plugin's check_fn. + +The raft platform adapter's ``check_raft_requirements()`` is registered as +the platform's ``check_fn``. This function is invoked on every +``load_gateway_config()`` call (dozens of times during normal gateway +operation). It must therefore be a *silent* predicate — returning True/False +without logging — otherwise every user without the ``raft`` CLI installed +gets their logs flooded with WARNING messages every few seconds. + +See: https://github.com/NousResearch/hermes-agent/issues/49234 +""" + +import logging +from unittest.mock import patch + +import pytest + + +@pytest.fixture +def raft_check(): + """Import check_raft_requirements fresh (adapter self-manages sys.path).""" + from plugins.platforms.raft.adapter import check_raft_requirements + + return check_raft_requirements + + +def test_check_returns_false_when_raft_cli_missing(raft_check): + """check_fn returns False when raft CLI is not in PATH.""" + with patch("plugins.platforms.raft.adapter.shutil.which", return_value=None), \ + patch("plugins.platforms.raft.adapter.AIOHTTP_AVAILABLE", True): + assert raft_check() is False + + +def test_check_returns_false_when_aiohttp_missing(raft_check): + """check_fn returns False when aiohttp dependency is unavailable.""" + with patch("plugins.platforms.raft.adapter.AIOHTTP_AVAILABLE", False): + assert raft_check() is False + + +def test_check_returns_true_when_all_deps_present(raft_check): + """check_fn returns True when all dependencies are available.""" + with patch("plugins.platforms.raft.adapter.shutil.which", return_value="/usr/bin/raft"), \ + patch("plugins.platforms.raft.adapter.AIOHTTP_AVAILABLE", True): + assert raft_check() is True + + +def test_check_silent_when_raft_cli_missing(raft_check, caplog): + """check_fn must NOT log a WARNING when raft CLI is missing. + + This is the regression guard for issue #49234 — logging inside check_fn + causes log spam because the function is called on every config load. + """ + with patch("plugins.platforms.raft.adapter.shutil.which", return_value=None), \ + patch("plugins.platforms.raft.adapter.AIOHTTP_AVAILABLE", True): + with caplog.at_level(logging.WARNING, logger="plugins.platforms.raft.adapter"): + raft_check() + + warnings = [r for r in caplog.records if r.levelno >= logging.WARNING] + assert warnings == [], ( + f"check_raft_requirements must be silent (no WARNING logs), " + f"but emitted: {[r.getMessage() for r in warnings]}" + ) + + +def test_check_silent_when_aiohttp_missing(raft_check, caplog): + """check_fn must NOT log a WARNING when aiohttp is missing.""" + with patch("plugins.platforms.raft.adapter.AIOHTTP_AVAILABLE", False): + with caplog.at_level(logging.WARNING, logger="plugins.platforms.raft.adapter"): + raft_check() + + warnings = [r for r in caplog.records if r.levelno >= logging.WARNING] + assert warnings == [], ( + f"check_raft_requirements must be silent (no WARNING logs), " + f"but emitted: {[r.getMessage() for r in warnings]}" + )