{"repo":"NousResearch/hermes-agent","public_id":"13640","display_label":"Deduplicating plugin tools against built-in toolsets","summary":"These issues and pull requests address duplicate entries appearing when plugin toolsets overlap with built-in toolset keys in the hermes tools output. Multiple fix attempts target the same deduplication problem from different angles.","subsystem":"skills","counts":{"members":4,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":1,"merged_prs":1,"attempt_prs":2},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":15634,"merged_at":"2026-04-25T12:53:08+00:00"},"resolution":{"state":"unresolved","by":null},"freshness":{"state":"stale","last_verified_at":"2026-04-25T12:53:08+00:00","is_stale":true},"grouping":{"backbone":"STRUCTURAL_SPANNING","link_density":0.5},"fix_edges":[{"issue_number":13640,"pr_number":15634,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false,"pr_merged_at":"2026-04-25T12:53:08+00:00"}],"members":[{"kind":"issue","number":13640,"title":"hermes tools duplicates built-in toolset rows for plugins attached to existing toolsets","url":null,"state":"closed"},{"kind":"pr","number":13804,"title":"fix(cli): deduplicate plugin toolsets against built-in keys in hermes tools (Closes #13640)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"ac05daa1890279ad5e6b50ea9944f6f337fc88d3","number":0},{"kind":"pr","value":"#15634","number":15634}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false},{"kind":"pr","number":14127,"title":"fix(tools): dedupe plugin-configurable toolsets","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"ac05daa18","number":0},{"kind":"pr","value":"#15634","number":15634},{"kind":"pr","value":"#13804","number":13804}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false},{"kind":"pr","number":15634,"title":"fix(tools): dedupe bundled plugin toolsets with built-in entries","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["hermes_cli/tools_config.py","tests/hermes_cli/test_tools_config.py"],"fix_hunks":[{"file":"hermes_cli/tools_config.py","hunks":["@@ -103,13 +103,23 @@ def _get_effective_configurable_toolsets():\n     \"\"\"Return CONFIGURABLE_TOOLSETS + any plugin-provided toolsets.\n \n     Plugin toolsets are appended at the end so they appear after the\n-    built-in toolsets in the TUI checklist.\n+    built-in toolsets in the TUI checklist. A plugin whose toolset key\n+    already appears in ``CONFIGURABLE_TOOLSETS`` is skipped — bundled\n+    plugins (e.g. ``plugins/spotify``) share their toolset key with the\n+    built-in entry, and we want the built-in label/description to win.\n+    Without the dedupe, ``hermes tools`` → \"reconfigure existing\" would\n+    list the same toolset twice.\n     \"\"\"\n     result = list(CONFIGURABLE_TOOLSETS)\n+    seen = {ts_key for ts_key, _, _ in result}\n     try:\n         from hermes_cli.plugins import discover_plugins, get_plugin_toolsets\n         discover_plugins()  # idempotent — ensures plugins are loaded\n-        result.extend(get_plugin_toolsets())\n+        for entry in get_plugin_toolsets():\n+            if entry[0] in seen:\n+                continue\n+            seen.add(entry[0])\n+            result.append(entry)\n     except Exception:\n         pass\n     return result"]},{"file":"tests/hermes_cli/test_tools_config.py","hunks":["@@ -766,3 +766,24 @@ def test_get_platform_tools_feishu_tools_not_on_other_platforms():\n         enabled = _get_platform_tools({}, plat)\n         assert \"feishu_doc\" not in enabled, f\"feishu_doc leaked onto {plat}\"\n         assert \"feishu_drive\" not in enabled, f\"feishu_drive leaked onto {plat}\"\n+\n+\n+def test_get_effective_configurable_toolsets_dedupes_bundled_plugins():\n+    \"\"\"Bundled plugins (plugins/spotify) share their toolset key with the\n+    built-in CONFIGURABLE_TOOLSETS entry. The effective list must not list\n+    them twice — otherwise `hermes tools` → \"reconfigure existing\" shows\n+    the same toolset two rows in a row.\n+    \"\"\"\n+    from hermes_cli.tools_config import _get_effective_configurable_toolsets\n+\n+    all_ts = _get_effective_configurable_toolsets()\n+    keys = [ts_key for ts_key, _, _ in all_ts]\n+    assert len(keys) == len(set(keys)), (\n+        f\"duplicate toolset keys in effective list: \"\n+        f\"{[k for k in keys if keys.count(k) > 1]}\"\n+    )\n+    # Spotify specifically — the bug that motivated the dedupe.\n+    spotify_rows = [t for t in all_ts if t[0] == \"spotify\"]\n+    assert len(spotify_rows) == 1, spotify_rows\n+    # Built-in label wins over the plugin label.\n+    assert spotify_rows[0][1] == \"🎵 Spotify\"\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}