From 46fe44fa8d28ec1c3c505c958b457e68ff6868bc Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Fri, 14 Aug 2026 23:06:59 -0700 Subject: [PATCH] test(cli): stub portable-MCP lookup in completer read test; bound resolve_toolset memo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - The readonly-loader completer test now stubs get_portable_mcp_server_names_nowait — real plugin discovery runs load_config() during one-time process init, which is not the per-keystroke read the test guards against. - Cap _resolve_toolset_memo at 256 entries: generation-keyed entries from stale generations are never hit again, so clear on overflow to keep long sessions bounded. --- tests/hermes_cli/test_completer_config_reads.py | 6 +++++- toolsets.py | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/hermes_cli/test_completer_config_reads.py b/tests/hermes_cli/test_completer_config_reads.py index 3318f44ae5..1db4814c50 100644 --- a/tests/hermes_cli/test_completer_config_reads.py +++ b/tests/hermes_cli/test_completer_config_reads.py @@ -52,9 +52,13 @@ class TestToolsCompletionsReadonlyConfig: return {} # The completer imports the loader inside the function, so patch the - # source module. + # source module. Portable-MCP lookup is stubbed because it triggers + # one-time plugin discovery (which legitimately calls load_config + # during process init) — this test asserts on the completer's own + # per-keystroke reads, not discovery's one-off startup reads. with patch("hermes_cli.config.load_config", counting_deepcopy), \ patch("hermes_cli.config.load_config_readonly", counting_readonly), \ + patch("hermes_cli.plugins.get_portable_mcp_server_names_nowait", lambda: set()), \ patch("hermes_cli.tools_config._get_plugin_toolset_keys", lambda: set()), \ patch("hermes_cli.tools_config._homeassistant_credentials_present", lambda: False), \ patch("hermes_cli.tools_config._xai_credentials_present", lambda: False): diff --git a/toolsets.py b/toolsets.py index 46ad8205d9..8bb86024f5 100644 --- a/toolsets.py +++ b/toolsets.py @@ -870,6 +870,10 @@ def resolve_toolset(name: str, visited: Set[str] = None, *, include_registry: bo except Exception: registry_id = 0 generation = 0 + # Entries from previous registry generations are never hit again; + # keep the memo bounded across long sessions with many MCP refreshes. + if len(_resolve_toolset_memo) >= 256: + _resolve_toolset_memo.clear() _resolve_toolset_memo[(name, include_registry, registry_id, generation)] = list(result) return result