fix(serve): announce READY sentinel on fd 1, not the redirected sys.stdout - #96284
fix(serve): announce READY sentinel on fd 1, not the redirected sys.stdout#96284kitsonk wants to merge 1 commit into
Conversation
…tdout Since 6d4e851 the serve startup path imports tui_gateway.server (for the flush-on-SIGTERM handlers) before the READY sentinel is printed. That module redirects sys.stdout to sys.stderr at import time, so the HERMES_(BACKEND|DASHBOARD)_READY port=<n> sentinel landed on stderr while the Electron desktop spawn watches child.stdout only — the desktop timed out after 90s and killed a perfectly healthy backend (issue NousResearch#96282). Write the sentinel to the real stdout file descriptor (fd 1 is untouched by the Python-level redirect), with a print() fallback. Adds a regression test that captures stdout/stderr separately — the existing E2E suite merges them, which is exactly how this slipped past CI.
Duplicate of #92631. Both patches repair the same READY-sentinel stdout routing regression that makes Desktop boot time out against a healthy backend. |
|
Local validation against current origin/main completed — no remote changes made. PR head: Current fetched origin/main: Merge compatibility: I reproduced the failure on unfixed current main using the real hermes_cli.web_server.start_server(...) path with stdout/stderr captured separately: With PR #96284: This confirms the PR’s root-cause analysis: Python-level sys.stdout has been redirected to stderr, while Desktop watches the child’s actual stdout pipe. Writing READY to fd 1 restores the handshake contract. Test results: One separate nonblocking finding: Full Electron launch validation was not completed because the local Linux Electron install is blocked by the Chrome SUID sandbox configuration (chrome-sandbox is not root-owned/mode 4755). I did not change system permissions or bypass the sandbox. So: the READY handshake fix is validated at the real subprocess/stdout boundary, but full packaged-Desktop launch validation was not performed on this machine. |
|
Corroboration: this same root cause was just filed separately as #96295 on Windows 11 (backend healthy, HERMES_BACKEND_READY present, but desktop stuck on 'Connecting…' and 'Timed out waiting for Hermes backend port announcement (90000ms)' after updating to v0.20.5). The fd-1 sentinel fix here resolves that report too, since the regression is cross-platform (the stdout redirect is platform-independent; the Windows desktop reads the same child.stdout). The separate-stdout/stderr regression test in this PR is exactly what would have caught the slip. Thanks for the clean minimal fix. |
|
Independently hit this on macOS 26.5.2 (arm64) right after today's v0.20.5 update — four 90s boot-timeout loops in desktop.log while every backend announced READY within ~1.2s (full evidence in #96282 (comment)). Verified the stream split locally: This |
Summary
Fixes #96282.
Since 6d4e851 (#94724 item 2) the serve startup path imports
tui_gateway.server(for the flush-on-SIGTERM handlers) before printingthe
HERMES_(BACKEND|DASHBOARD)_READY port=<n>sentinel. That moduleredirects
sys.stdouttosys.stderrat import time (to keep stray printsoff the JSON-RPC protocol stream), so the sentinel now lands on stderr —
while the Electron desktop spawn (
apps/desktop/electron/backend-ready.ts,waitForDashboardPort) watches child.stdout only.Result: the backend is healthy and announces its port in ~2s, but the desktop
times out after 90s ("Timed out waiting for Hermes backend port announcement"),
SIGTERMs a perfectly good backend, and the renderer loops
refreshProfiles failed after 3 attempt(s).Repro on current main:
Fix
Write the sentinel to the real stdout file descriptor — fd 1 is the pipe the
desktop reads and is untouched by the Python-level
sys.stdoutredirect —with a
print()fallback for exotic environments where fd 1 isn't writable(e.g. closed):
Test plan
test_ready_sentinel_arrives_on_stdout_not_stderrin
tests/hermes_cli/test_serve_port_in_use.py— spawns a real servewith stdout/stderr captured separately and asserts the sentinel
arrives on stdout. The existing E2E tests merge stderr into stdout,
which is exactly how the regression slipped past CI.
desktop's 90s-timeout failure mode) and passes with this patch.
tests/hermes_cli/test_serve_port_in_use.pysuite passes with thepatch (existing conflict/ephemeral-port contracts untouched).
and the desktop connects (reporter-verified).