From ee7af858845695c370f60789b0ad62eea450248e Mon Sep 17 00:00:00 2001 From: ai-ag2026 <261867348+ai-ag2026@users.noreply.github.com> Date: Sun, 12 Jul 2026 02:34:24 +0200 Subject: [PATCH] fix(ctl): keep listener diagnostics best-effort --- ctl.sh | 5 +++- tests/test_ctl_foreign_server_guard.py | 34 ++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/ctl.sh b/ctl.sh index f693aaee..fe26185f 100755 --- a/ctl.sh +++ b/ctl.sh @@ -491,7 +491,10 @@ _port_listener_diag() { if [[ -z "${line}" ]] && command -v lsof >/dev/null 2>&1; then line="$(lsof -nP -iTCP:"${port}" -sTCP:LISTEN 2>/dev/null | awk 'NR==2 {print; exit}')" fi - [[ -n "${line}" ]] && printf '%s' "${line}" + if [[ -n "${line}" ]]; then + printf '%s' "${line}" + fi + return 0 } _systemd_webui_conflict() { diff --git a/tests/test_ctl_foreign_server_guard.py b/tests/test_ctl_foreign_server_guard.py index 6fe402c8..b095cf05 100644 --- a/tests/test_ctl_foreign_server_guard.py +++ b/tests/test_ctl_foreign_server_guard.py @@ -345,6 +345,40 @@ def test_status_reports_unmanaged_running_instance(tmp_path): _stop_proc(server) +@pytest.mark.skipif(sys.platform == "win32", reason="POSIX daemon guards") +@pytest.mark.parametrize( + ("command", "expected_output"), + [ + ("status", "running (not managed by ctl.sh)"), + ("stop", "NOT managed by ctl.sh"), + ], +) +def test_unmanaged_instance_commands_survive_missing_listener_diagnostics( + tmp_path, command, expected_output +): + """Best-effort listener diagnostics must not abort status/stop under set -e + when ss/lsof are unavailable or return no matching listener row.""" + fake_bin = tmp_path / "bin" + fake_bin.mkdir() + _write_fake_port_tools(fake_bin, pid_listens=False) + port = _free_port() + server = _start_dummy_http_server(port) + try: + result = run_ctl( + tmp_path, + command, + env=_guard_env(fake_bin, HERMES_WEBUI_PORT=str(port)), + timeout=15, + ) + combined = result.stdout + result.stderr + assert result.returncode == 0, combined + assert expected_output in combined + assert "Listener:" not in combined + assert server.poll() is None, "ctl.sh must leave a foreign server untouched" + finally: + _stop_proc(server) + + @pytest.mark.skipif(sys.platform == "win32", reason="POSIX daemon guards") def test_stop_warns_about_unmanaged_instance_and_leaves_it_alone(tmp_path): port = _free_port()