From f28ca2f85a52c0c8eed01734dccfa9b291ab4dca Mon Sep 17 00:00:00 2001 From: ConTresillo <> Date: Sun, 4 Oct 2026 02:29:28 +0530 Subject: [PATCH] fix(whatsapp): fall back when the parent job forbids CREATE_BREAKAWAY_FROM_JOB MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Baileys bridge is spawned with windows_detach_popen_kwargs(), whose default Windows flags include CREATE_BREAKAWAY_FROM_JOB. Per MSDN, that flag is only valid when the parent's job object sets JOB_OBJECT_LIMIT_BREAKAWAY_OK; otherwise CreateProcess fails with ERROR_ACCESS_DENIED (WinError 5). Electron (Hermes Desktop) and Task Scheduler both wrap children in jobs that do not grant breakaway, so on Windows the bridge could never start from either launch path and WhatsApp stayed permanently "Disconnected". Observed on Windows 10 with Hermes_Gateway (Task Scheduler) and a Desktop-initiated restart; the live gateway's job reported LimitFlags=0x0. Catch PermissionError and retry with windows_detach_flags_without_breakaway() — the fallback already documented in _subprocess_compat.windows_detach_flags but not used by this caller. The retry keeps the bridge in the gateway's job, so it now dies with the gateway instead of outliving it. Co-Authored-By: Claude Opus 4.8 (1M context) --- plugins/platforms/whatsapp/adapter.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/plugins/platforms/whatsapp/adapter.py b/plugins/platforms/whatsapp/adapter.py index 6d50a5326ef06..ef0db9e603b44 100644 --- a/plugins/platforms/whatsapp/adapter.py +++ b/plugins/platforms/whatsapp/adapter.py @@ -17,7 +17,10 @@ from gateway.platforms._shared import ( apply_yaml_bridge as _apply_yaml_bridge, extra_or_secret as _extra_or_secret, get_scoped_secret, send_error ) -from hermes_cli._subprocess_compat import windows_detach_popen_kwargs +from hermes_cli._subprocess_compat import ( + windows_detach_flags_without_breakaway, + windows_detach_popen_kwargs, +) from hermes_constants import (find_node_executable, get_hermes_dir, with_hermes_node_path) _IS_WINDOWS = platform.system() == "Windows" @@ -534,9 +537,22 @@ async def connect(self, *, is_reconnect: bool = False) -> bool: node = find_node_executable("node") if node is None: raise RuntimeError("Node.js is no longer available; run `hermes pm install`") - self._bridge_process = subprocess.Popen( - [node, str(bridge_path), "--port", str(self._bridge_port), "--session", str(self._session_path), - "--mode", _wenv("WHATSAPP_MODE", "self-chat")], stdout=bridge_log_fh, stderr=bridge_log_fh, env=self._bridge_env(), **windows_detach_popen_kwargs()) + bridge_argv = [node, str(bridge_path), "--port", str(self._bridge_port), "--session", str(self._session_path), + "--mode", _wenv("WHATSAPP_MODE", "self-chat")] + try: + self._bridge_process = subprocess.Popen( + bridge_argv, stdout=bridge_log_fh, stderr=bridge_log_fh, env=self._bridge_env(), **windows_detach_popen_kwargs()) + except PermissionError: + # CREATE_BREAKAWAY_FROM_JOB is rejected with ERROR_ACCESS_DENIED unless the parent's job + # sets JOB_OBJECT_LIMIT_BREAKAWAY_OK. Electron (Hermes Desktop) and Task Scheduler both + # wrap children in jobs that forbid it, so the default detach flags can never spawn the + # bridge there and WhatsApp stays permanently "Disconnected". Retry inside the parent's + # job — the documented fallback in windows_detach_flags_without_breakaway. Tradeoff: the + # bridge then dies with the gateway instead of outliving it. + logger.warning("[%s] Bridge spawn denied by parent job (breakaway forbidden); retrying without CREATE_BREAKAWAY_FROM_JOB", self.name) + self._bridge_process = subprocess.Popen( + bridge_argv, stdout=bridge_log_fh, stderr=bridge_log_fh, env=self._bridge_env(), + creationflags=windows_detach_flags_without_breakaway()) _write_bridge_pidfile(self._session_path, self._bridge_process.pid) if not await self._wait_for_bridge(): return False