From 3ec5fb076ed0d9bcc9e2bd192036f2211a5da054 Mon Sep 17 00:00:00 2001 From: hereicq Date: Sun, 26 Jul 2026 09:22:36 +0800 Subject: [PATCH] fix(gateway): survive faulthandler.enable() when sys.stderr is None MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit faulthandler.enable() writes to sys.stderr by default, and raises RuntimeError('sys.stderr is None') when the gateway is launched without an attached console — e.g. via the Windows Startup VBS shim, pythonw.exe, a detached service, or any parent that redirects stderr to DEVNULL. Because this happens on the very first line of GatewayRunner.start(), the whole gateway used to die at startup and every configured platform adapter (Discord bot, Telegram, Slack, …) would silently show offline until the user manually re-ran 'hermes gateway run --replace' from a real terminal. Wrap the call and fall back to a log-file file descriptor (logs/gateway_faulthandler.log) when stderr is unavailable, so fatal-error stack dumps still land somewhere useful. If even the fallback fails we log-and-continue rather than kill the gateway. Repro traceback (from a real user's gateway-exit-diag.log, launched via the Startup VBS with stdin_is_tty=false): File "gateway/run.py", line 7821, in start faulthandler.enable() RuntimeError: sys.stderr is None --- gateway/run.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/gateway/run.py b/gateway/run.py index 4201dde1ca58..1c6a8a839738 100644 --- a/gateway/run.py +++ b/gateway/run.py @@ -7823,10 +7823,24 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew Returns True if at least one adapter connected successfully. """ logger.info("Starting Hermes Gateway...") - # Enable faulthandler at gateway start so that SIGUSR2 (or an - # internal watchdog) can dump all thread and task stacks to stderr - # for post-mortem diagnosis of event-loop freezes (#70344). - faulthandler.enable() + # Enable faulthandler for stack dumps on freezes/crashes (#70344). + # Falls back to a log file when sys.stderr is None (Windows VBS / + # pythonw / detached service) — otherwise the gateway would die + # here and take every adapter offline. See #71671. + try: + faulthandler.enable() + except (RuntimeError, ValueError, OSError): + try: + _fh_log_dir = getattr(self.config, "log_dir", None) or os.path.join( + os.environ.get("HERMES_HOME", str(Path.home() / ".hermes")), + "logs", + ) + os.makedirs(_fh_log_dir, exist_ok=True) + _fh_enable_path = os.path.join(_fh_log_dir, "gateway_faulthandler.log") + _fh_enable_file = open(_fh_enable_path, "a", encoding="utf-8") + faulthandler.enable(file=_fh_enable_file, all_threads=True) + except Exception: + logger.debug("faulthandler.enable() unavailable", exc_info=True) # Also dump stacks to a rotating file for off-line analysis when # the gateway is running under a service manager that doesn't # capture stderr.