From 42e1aa39fce37cf3640599ff3e25dd1e2bf18ae4 Mon Sep 17 00:00:00 2001 From: kshitijk4poor <82637225+kshitijk4poor@users.noreply.github.com> Date: Thu, 27 Aug 2026 16:56:32 +0530 Subject: [PATCH] fix(serve): widen fd-1 sentinel write to BACKEND_PORT_IN_USE sibling site MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The same stdout redirect that rerouted the READY sentinel (#96282) also reroutes the machine-parsed BACKEND_PORT_IN_USE sentinel printed by _report_port_in_use() — both preflight and probe-to-bind-race callers run after tui_gateway.server's sys.stdout=sys.stderr swap. Extract the fd-1 write into _write_machine_sentinel_line() and use it at both sentinel sites; human-facing hint lines stay on print(). --- hermes_cli/web_server.py | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/hermes_cli/web_server.py b/hermes_cli/web_server.py index 62547f08fc..bbf023b8b4 100644 --- a/hermes_cli/web_server.py +++ b/hermes_cli/web_server.py @@ -19515,9 +19515,27 @@ def _port_bind_conflict(host: str, port: int) -> bool: return False +def _write_machine_sentinel_line(line: str) -> None: + """Write a machine-parsed sentinel line to the REAL stdout (fd 1). + + The serve startup path imports ``tui_gateway.server`` (flush-on-SIGTERM + handlers, #94724) which redirects ``sys.stdout`` to ``sys.stderr`` at + import time to keep stray prints off the JSON-RPC protocol stream. Any + machine-readable sentinel printed after that import via ``print()`` lands + on stderr — invisible to consumers that parse the child's stdout pipe + (the Desktop spawn, scripts). fd 1 is untouched by the Python-level + redirect, so write there; fall back to ``print`` for exotic environments + where fd 1 isn't writable (e.g. closed). + """ + try: + os.write(1, (line + "\n").encode()) + except OSError: + print(line, flush=True) + + def _report_port_in_use(host: str, port: int) -> None: """Print the machine sentinel + a human hint naming likely holders.""" - print(_PORT_IN_USE_SENTINEL.format(port=port), flush=True) + _write_machine_sentinel_line(_PORT_IN_USE_SENTINEL.format(port=port)) print( f" Port {port} on {host} is already in use — likely another " "'hermes serve' / 'hermes dashboard' backend or the Hermes gateway. " @@ -19894,12 +19912,8 @@ def start_server( # still the real stdout — and the Desktop spawn watches # child.stdout for this sentinel — so write to the fd, not to the # (redirected) sys.stdout, or the desktop times out after 90s - # against a perfectly healthy backend. - _ready_line = f"{ready_token} port={actual_port}\n" - try: - os.write(1, _ready_line.encode()) - except OSError: - print(_ready_line, end="", flush=True) + # against a perfectly healthy backend (#96282). + _write_machine_sentinel_line(f"{ready_token} port={actual_port}") if headless: # No SPA, and the JSON-RPC/WS endpoints are auth-gated — don't # advertise a paste-and-connect URL, just announce the bind.