From fa3471d462cac8f31a2b1d1c3f04277c68d82217 Mon Sep 17 00:00:00 2001 From: HexLab98 Date: Fri, 28 Aug 2026 12:40:22 +0700 Subject: [PATCH] fix(cli): print partial-update hint when chat startup hits a first-party ImportError (#96900) HermesCLI construction imports helpers from hermes_cli.config before the agent-setup mixin can run, so a mixed-version tree crashed with a raw traceback. Catch that ImportError on the chat entry path and tell the user to run hermes update. --- cli.py | 41 +++++++++++++++++++++++++---------------- hermes_cli/main.py | 16 +++++++++++++--- hermes_constants.py | 16 ++++++++++++++++ 3 files changed, 54 insertions(+), 19 deletions(-) diff --git a/cli.py b/cli.py index 1592803558..3e69298bdc 100644 --- a/cli.py +++ b/cli.py @@ -21822,22 +21822,31 @@ def main( parsed_skills = _parse_skills_argument(skills) # Create CLI instance - cli = HermesCLI( - model=model, - toolsets=toolsets_list, - provider=provider, - reasoning=reasoning, - api_key=api_key, - base_url=base_url, - max_turns=max_turns, - run_budget=run_budget, - verbose=verbose, - compact=compact, - resume=resume, - checkpoints=checkpoints, - pass_session_id=pass_session_id, - ignore_rules=ignore_rules, - ) + try: + cli = HermesCLI( + model=model, + toolsets=toolsets_list, + provider=provider, + reasoning=reasoning, + api_key=api_key, + base_url=base_url, + max_turns=max_turns, + run_budget=run_budget, + verbose=verbose, + compact=compact, + resume=resume, + checkpoints=checkpoints, + pass_session_id=pass_session_id, + ignore_rules=ignore_rules, + ) + except ImportError as e: + # Direct `python cli.py` / `python -m cli` bypasses cmd_chat's + # ImportError handler. Same mixed-tree class as #96900. + from hermes_constants import emit_partial_update_hint + + if emit_partial_update_hint(e): + sys.exit(1) + raise if parsed_skills: # Load the skill payloads in the background: skill_view walks the diff --git a/hermes_cli/main.py b/hermes_cli/main.py index d4e6408d94..c84c38a7c9 100644 --- a/hermes_cli/main.py +++ b/hermes_cli/main.py @@ -3399,9 +3399,6 @@ def cmd_chat(args): accept_hooks=getattr(args, "accept_hooks", False), ) - # Import and run the CLI - from cli import main as cli_main - # --query-file: read the single query from a file (or stdin via '-') so # callers never have to shell-quote message bodies. This is the transport # the Bot Mode DM protocol uses — interpolating arbitrary text into a @@ -3453,10 +3450,23 @@ def cmd_chat(args): kwargs = {k: v for k, v in kwargs.items() if v is not None} try: + from cli import main as cli_main + cli_main(**kwargs) except ValueError as e: print(f"Error: {e}") sys.exit(1) + except ImportError as e: + # Mixed-version installs (new cli.py, older hermes_cli.config) crash + # here — e.g. missing resolve_turn_limit / split_model_config_default + # (#96900). The agent-setup mixin prints this hint too late: HermesCLI + # construction already failed. Fast-chat launch also goes through + # cmd_chat, so this one catch covers `hermes` / `hermes chat`. + from hermes_constants import emit_partial_update_hint + + if emit_partial_update_hint(e): + sys.exit(1) + raise def cmd_gateway(args): diff --git a/hermes_constants.py b/hermes_constants.py index ccde302f47..4bd9d583b2 100644 --- a/hermes_constants.py +++ b/hermes_constants.py @@ -1860,3 +1860,19 @@ def partial_update_hint(exc: BaseException) -> list[str]: " hermes update", "If that also fails, reinstall: https://hermes-agent.nousresearch.com", ] + + +def emit_partial_update_hint(exc: BaseException, *, file=None) -> bool: + """Print recovery guidance for a half-updated tree. + + Returns True when guidance was written (caller should then exit), False + when *exc* is not a first-party ``ImportError`` (caller should re-raise). + """ + lines = partial_update_hint(exc) + if not lines: + return False + out = sys.stderr if file is None else file + print(f"Error: {exc}", file=out) + for line in lines: + print(line, file=out) + return True