fix: detect non-interactive TTY in setup wizard to prevent hang
hermes setup hung indefinitely on headless SSH sessions, Docker containers, and CI/CD environments because the interactive provider selection menu could not receive input. Two-layer fix: 1. sys.stdin.isatty() check — auto-detects non-interactive environments 2. --non-interactive flag support — already in CLI parser, now honored In both cases the wizard exits immediately with helpful guidance pointing users to 'hermes config set' commands. Closes #905
This commit is contained in:
parent
728fa66ef0
commit
4aa94ae7cc
2 changed files with 65 additions and 0 deletions
|
|
@ -2338,6 +2338,28 @@ def run_setup_wizard(args):
|
|||
config = load_config()
|
||||
hermes_home = get_hermes_home()
|
||||
|
||||
# Detect non-interactive environments (headless SSH, Docker, CI/CD)
|
||||
non_interactive = getattr(args, 'non_interactive', False)
|
||||
if not non_interactive and not sys.stdin.isatty():
|
||||
non_interactive = True
|
||||
|
||||
if non_interactive:
|
||||
print()
|
||||
print(color("⚕ Hermes Setup — Non-interactive mode", Colors.CYAN, Colors.BOLD))
|
||||
print()
|
||||
print_info("Running in a non-interactive environment (no TTY detected).")
|
||||
print_info("The interactive wizard cannot be used here.")
|
||||
print()
|
||||
print_info("Configure Hermes using environment variables or config commands:")
|
||||
print_info(" hermes config set model.provider custom")
|
||||
print_info(" hermes config set model.base_url http://localhost:8080/v1")
|
||||
print_info(" hermes config set model.default your-model-name")
|
||||
print()
|
||||
print_info("Or set OPENROUTER_API_KEY / OPENAI_API_KEY in your environment.")
|
||||
print_info("Run 'hermes setup' in an interactive terminal to use the full wizard.")
|
||||
print()
|
||||
return
|
||||
|
||||
# Check if a specific section was requested
|
||||
section = getattr(args, "section", None)
|
||||
if section:
|
||||
|
|
|
|||
43
tests/hermes_cli/test_setup_noninteractive.py
Normal file
43
tests/hermes_cli/test_setup_noninteractive.py
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
"""Tests for non-interactive setup wizard behavior."""
|
||||
import pytest
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
|
||||
def _make_args(**kwargs):
|
||||
args = MagicMock()
|
||||
args.non_interactive = kwargs.get("non_interactive", False)
|
||||
args.section = kwargs.get("section", None)
|
||||
args.reset = kwargs.get("reset", False)
|
||||
return args
|
||||
|
||||
|
||||
class TestNonInteractiveSetup:
|
||||
"""Verify setup wizard exits cleanly in non-interactive environments."""
|
||||
|
||||
def test_non_interactive_flag_skips_wizard(self, capsys):
|
||||
"""--non-interactive flag should print help and return without hanging."""
|
||||
from hermes_cli.setup import run_setup_wizard
|
||||
args = _make_args(non_interactive=True)
|
||||
|
||||
with patch("hermes_cli.setup.ensure_hermes_home"), \
|
||||
patch("hermes_cli.setup.load_config", return_value={}), \
|
||||
patch("hermes_cli.setup.get_hermes_home", return_value="/tmp/.hermes"):
|
||||
run_setup_wizard(args)
|
||||
|
||||
out = capsys.readouterr().out
|
||||
assert "hermes config set" in out
|
||||
|
||||
def test_no_tty_skips_wizard(self, capsys):
|
||||
"""When stdin has no TTY, wizard should exit with helpful message."""
|
||||
from hermes_cli.setup import run_setup_wizard
|
||||
args = _make_args(non_interactive=False)
|
||||
|
||||
with patch("hermes_cli.setup.ensure_hermes_home"), \
|
||||
patch("hermes_cli.setup.load_config", return_value={}), \
|
||||
patch("hermes_cli.setup.get_hermes_home", return_value="/tmp/.hermes"), \
|
||||
patch("sys.stdin") as mock_stdin:
|
||||
mock_stdin.isatty.return_value = False
|
||||
run_setup_wizard(args)
|
||||
|
||||
out = capsys.readouterr().out
|
||||
assert "hermes config set" in out
|
||||
Loading…
Add table
Add a link
Reference in a new issue