From ae1c11c5a512be58c64ce31c2213c2c9ee5079bb Mon Sep 17 00:00:00 2001 From: Bartok Moltbot Date: Wed, 11 Mar 2026 03:33:27 -0400 Subject: [PATCH] fix(cli): resolve duplicate 'skills' subparser crash on Python 3.11+ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #898 — Python 3.11 changed argparse to raise an exception on duplicate subparser names (CPython #94331). The 'skills' name was registered twice: once for Skills Hub and once for skills config. Changes: - Remove duplicate 'skills' subparser registration - Add 'config' as a sub-action under the existing 'hermes skills' command - Route 'hermes skills config' to skills_config module - Add regression test to catch future duplicates Migration: 'hermes skills' (config) is now 'hermes skills config' --- hermes_cli/main.py | 29 ++++++++----------- tests/hermes_cli/test_skills_subparser.py | 35 +++++++++++++++++++++++ 2 files changed, 47 insertions(+), 17 deletions(-) create mode 100644 tests/hermes_cli/test_skills_subparser.py diff --git a/hermes_cli/main.py b/hermes_cli/main.py index 62497ded0..480aba7bf 100644 --- a/hermes_cli/main.py +++ b/hermes_cli/main.py @@ -2252,8 +2252,8 @@ For more help on a command: # ========================================================================= skills_parser = subparsers.add_parser( "skills", - help="Skills Hub — search, install, and manage skills from online registries", - description="Search, install, inspect, audit, and manage skills from GitHub, ClawHub, and other registries." + help="Search, install, configure, and manage skills", + description="Search, install, inspect, audit, configure, and manage skills from GitHub, ClawHub, and other registries." ) skills_subparsers = skills_parser.add_subparsers(dest="skills_action") @@ -2307,9 +2307,17 @@ For more help on a command: tap_rm = tap_subparsers.add_parser("remove", help="Remove a tap") tap_rm.add_argument("name", help="Tap name to remove") + # config sub-action: interactive enable/disable + skills_subparsers.add_parser("config", help="Interactive skill configuration — enable/disable individual skills") + def cmd_skills(args): - from hermes_cli.skills_hub import skills_command - skills_command(args) + # Route 'config' action to skills_config module + if getattr(args, 'skills_action', None) == 'config': + from hermes_cli.skills_config import skills_command as skills_config_command + skills_config_command(args) + else: + from hermes_cli.skills_hub import skills_command + skills_command(args) skills_parser.set_defaults(func=cmd_skills) @@ -2332,19 +2340,6 @@ For more help on a command: tools_command(args) tools_parser.set_defaults(func=cmd_tools) - - # ========================================================================= - # skills command - # ========================================================================= - skills_parser = subparsers.add_parser( - "skills", - help="Configure which skills are enabled", - description="Interactive skill configuration — enable/disable individual skills." - ) - def cmd_skills(args): - from hermes_cli.skills_config import skills_command - skills_command(args) - skills_parser.set_defaults(func=cmd_skills) # ========================================================================= # sessions command # ========================================================================= diff --git a/tests/hermes_cli/test_skills_subparser.py b/tests/hermes_cli/test_skills_subparser.py new file mode 100644 index 000000000..d2b89ed3e --- /dev/null +++ b/tests/hermes_cli/test_skills_subparser.py @@ -0,0 +1,35 @@ +"""Test that skills subparser doesn't conflict (regression test for #898).""" + +import argparse + + +def test_no_duplicate_skills_subparser(): + """Ensure 'skills' subparser is only registered once to avoid Python 3.11+ crash. + + Python 3.11 changed argparse to raise an exception on duplicate subparser + names instead of silently overwriting (see CPython #94331). + + This test will fail with: + argparse.ArgumentError: argument command: conflicting subparser: skills + + if the duplicate 'skills' registration is reintroduced. + """ + # Force fresh import of the module where parser is constructed + # If there are duplicate 'skills' subparsers, this import will raise + # argparse.ArgumentError at module load time + import importlib + import sys + + # Remove cached module if present + if 'hermes_cli.main' in sys.modules: + del sys.modules['hermes_cli.main'] + + try: + import hermes_cli.main # noqa: F401 + except argparse.ArgumentError as e: + if "conflicting subparser" in str(e): + raise AssertionError( + f"Duplicate subparser detected: {e}. " + "See issue #898 for details." + ) from e + raise