From 889a13696bf12995ac5288c613b825c78f6a4899 Mon Sep 17 00:00:00 2001 From: kshitijk4poor <82637225+kshitijk4poor@users.noreply.github.com> Date: Fri, 12 Jun 2026 10:55:44 +0530 Subject: [PATCH] fix(plugins): clear _plugin_platform_names on force-rediscover discover_and_load(force=True) cleared every per-plugin registry except _plugin_platform_names, which register_platform() populates. A platform plugin disabled between force-rediscovers left a stale name behind, so the set diverged from the real platform_registry / _plugins state and never shrank across repeated force passes. Add the missing clear() and a regression test that seeds every per-plugin registry, forces a rediscover, and asserts they all empty (so a future registry addition can't silently leak across a force pass either). --- hermes_cli/plugins.py | 1 + tests/hermes_cli/test_plugins.py | 44 ++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/hermes_cli/plugins.py b/hermes_cli/plugins.py index 3de41edf4629..87e46de82906 100644 --- a/hermes_cli/plugins.py +++ b/hermes_cli/plugins.py @@ -1129,6 +1129,7 @@ class PluginManager: self._hooks.clear() self._middleware.clear() self._plugin_tool_names.clear() + self._plugin_platform_names.clear() self._cli_commands.clear() self._plugin_commands.clear() self._plugin_skills.clear() diff --git a/tests/hermes_cli/test_plugins.py b/tests/hermes_cli/test_plugins.py index 6e424e98450e..cd6922a6d80b 100644 --- a/tests/hermes_cli/test_plugins.py +++ b/tests/hermes_cli/test_plugins.py @@ -439,6 +439,50 @@ class TestPluginDiscovery: assert "ep_plugin" in mgr._plugins + def test_force_rediscover_clears_all_plugin_registries(self, monkeypatch): + """force=True must clear every plugin-populated registry. + + Regression: ``_plugin_platform_names`` was populated by + ``register_platform`` but omitted from the ``discover_and_load(force=True)`` + clear block, so a platform plugin disabled between force-rediscovers + left a stale entry behind forever (the set diverged from the real + platform_registry / _plugins truth). This asserts the clear block + empties the full set of per-plugin registries so no future addition + silently leaks across a force pass either. + """ + mgr = PluginManager() + + # Seed every registry that a plugin's register() can populate, then + # mark discovery done so force=True takes the clear path (we stub the + # inner sweep so the test doesn't depend on any on-disk plugins). + mgr._plugins["p"] = MagicMock() + mgr._hooks["pre_tool_call"] = [lambda **_: None] + mgr._middleware["llm_request"] = [lambda **_: None] + mgr._plugin_tool_names.add("some_tool") + mgr._plugin_platform_names.add("irc") + mgr._cli_commands["c"] = {"plugin": "p"} + mgr._plugin_commands["cmd"] = {"plugin": "p"} + mgr._plugin_skills["p:skill"] = {} + mgr._aux_tasks["task"] = {"plugin": "p"} + mgr._slack_action_handlers.append(("aid", lambda **_: None, "p")) + mgr._discovered = True + + monkeypatch.setattr(PluginManager, "_discover_and_load_inner", lambda self_inner: None) + mgr.discover_and_load(force=True) + + assert mgr._plugins == {} + assert mgr._hooks == {} + assert mgr._middleware == {} + assert mgr._plugin_tool_names == set() + assert mgr._plugin_platform_names == set(), ( + "_plugin_platform_names was not cleared on force-rediscover" + ) + assert mgr._cli_commands == {} + assert mgr._plugin_commands == {} + assert mgr._plugin_skills == {} + assert mgr._aux_tasks == {} + assert mgr._slack_action_handlers == [] + # ── TestPluginLoading ──────────────────────────────────────────────────────