fix(gateway,plugins): make discord disable actually disable, stop reconnect storm (#30736)#30762
Open
xxxigm wants to merge 3 commits into
Open
fix(gateway,plugins): make discord disable actually disable, stop reconnect storm (#30736)#30762xxxigm wants to merge 3 commits into
xxxigm wants to merge 3 commits into
Conversation
…usResearch#30736) `hermes plugins list` and `hermes plugins disable` always referred to bundled platform plugins by their path-derived key (`platforms/discord`, `platforms/teams`, …). But the loader scanned `plugins/platforms/` with no category prefix, which keyed those same plugins by their manifest name (`discord-platform`, `teams-platform`, …). The mismatch meant `hermes plugins disable platforms/discord` saved `platforms/discord` to `plugins.disabled`, the loader looked up `discord-platform`, no match fired, and the plugin loaded as if nothing happened. Drop `platforms` from `skip_names` and remove the redundant `scan_directory(plugins/platforms)` so the recursive scanner picks the directory up as a category. Bundled platform plugins now register under `platforms/<name>`, matching what the CLI reports and what the disable command writes. Also expose `is_platform_plugin_disabled(name)` so the gateway runtime can check either key form (path-derived or legacy manifest name) without re-implementing the matching logic.
…Research#30736) When a platform's `enabled` flag in config.yaml is still true but the user has explicitly disabled the matching plugin (e.g. `hermes plugins disable platforms/discord`), the startup loop now short-circuits before `_create_adapter`. Without this guard the adapter would be instantiated, `connect()` would fail with "No bot token configured", the platform would be queued into the reconnect watcher, and the user would see an ERROR-per-attempt log storm forever after — exactly the behaviour reported in NousResearch#30736. The skip path: * logs one INFO line that names the platform and the command needed to re-enable it (`hermes plugins enable platforms/<name>`) or to silence the notice (`platforms.<name>.enabled=false`), * records the runtime status as `disabled` so `gateway status` / dashboards reflect the actual situation, * never enters `_failed_platforms`, so the reconnect watcher never fires for it. Wrapped in `try/except` so a corrupt config can't crash startup — the gateway falls back to the existing adapter path in that case.
…usResearch#30736) 17 tests across three classes: * `TestBundledPlatformKeyAlignment` — pins every bundled platform adapter under `platforms/<name>` so any future refactor that reintroduces the manifest-name key fails immediately. * `TestDisableHonoursBothKeyForms` — covers the new path-derived key AND the legacy manifest-name key (back-compat for hand-edited configs), plus `is_platform_plugin_disabled()` argument hardening. * `TestGatewaySkipsDisabledPlatformPlugin` — boots `GatewayRunner` with a sentinel `_create_adapter` that raises if invoked for the disabled platform. Asserts the platform is skipped before adapter creation, a single actionable INFO log fires, no ERROR/WARNING leaks, the `_failed_platforms` reconnect queue stays clean, and that lookup failures fall through to the legacy adapter path.
This was referenced May 23, 2026
|
Sick of discord. But this PR is a bit too big... |
I'm afraid a simple |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


What does this PR do?
Fixes #30736: when a user runs
hermes plugins disable platforms/discord, the gateway still loaded the discord adapter, spammedERROR hermes_plugins.discord_platform.adapter: [Discord] No bot token configuredon every start AND every reconnect attempt, and queued discord into the reconnect watcher until the per-platform circuit breaker paused it after 10 failures.Root cause is a plugin-key mismatch between the CLI and the loader.
hermes plugins list/hermes plugins disableuse the path-derived keyplatforms/discord, whilePluginManager.discover_and_loadscannedplugins/platforms/with no category prefix and therefore registered the bundled adapter under its manifest name (discord-platform). The disabled-check OR'ed both forms but neither matched, so the plugin loaded as if nothing happened. The bug affects every bundled platform plugin (discord, teams, irc, line, simplex, google_chat).This PR fixes the root cause and hardens the gateway against the same class of misconfiguration so future drift cannot reproduce the original symptom.
Related Issue
Closes #30736
Type of Change
Changes Made
hermes_cli/plugins.py— fix bundled platform key derivation + add helper (+39 / −10):"platforms"fromskip_namesso the recursive scanner picks upplugins/platforms/as a category and produces path-derived keys (platforms/discord,platforms/teams, …).scan_directory(plugins/platforms)block that would otherwise produce duplicate entries under both keys.is_platform_plugin_disabled(name)helper that resolves both the new path-derived key and the legacy manifest-name key, so callers don't re-implement the matching logic.gateway/run.py— skip disabled platform plugins cleanly at startup (+32):_create_adapter, callis_platform_plugin_disabled(platform.value). If true: log one INFO line that names the platform and the command to re-enable it (hermes plugins enable platforms/<name>) or silence the notice (platforms.<name>.enabled=false), mark runtime status asdisabled, and skip the adapter entirely so no_failed_platformsentry / no reconnect watcher activity / no ERROR log spam.try/exceptso a corrupt config can't crash startup — the gateway falls through to the legacy adapter path in that case.tests/gateway/test_disabled_platform_plugin_30736.py— 17 new test cases across 3 classes (+472 lines):TestBundledPlatformKeyAlignment— pins every bundled platform adapter underplatforms/<name>so any future refactor that re-introduces the manifest-name key fails immediately.TestDisableHonoursBothKeyForms— covers the new path-derived key AND the legacy manifest-name key (back-compat for hand-edited configs), plusis_platform_plugin_disabled()argument hardening ("",None).TestGatewaySkipsDisabledPlatformPlugin— bootsGatewayRunnerwith a sentinel_create_adapterthat raises if invoked for the disabled platform; asserts the platform is skipped before adapter creation, a single actionable INFO log fires, no ERROR/WARNING leaks,_failed_platformsstays clean, the platform-configenabled=Falseshort-circuit still runs first, and exceptions inside the lookup are caught.How to Test
.venvis set up:python3 -m venv .venv && source .venv/bin/activate && pip install -e ".[all,dev]"Checklist
Code
fix(plugins): …,fix(gateway): …,test(gateway): …)scripts/run_tests.sh tests/gateway/test_disabled_platform_plugin_30736.pyand all tests passDocumentation & Housekeeping
docs/, docstrings) — N/A (no user-facing CLI/config change)cli-config.yaml.exampleif I added/changed config keys — N/A (no new config keys)CONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — N/Amonkeypatch+ mocks, no real filesystem I/O outsidetmp_path)Screenshots / Logs