Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion hermes_cli/_subprocess_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,10 +195,16 @@ def windows_hide_flags() -> int:
``DETACHED_PROCESS`` β€” the child still inherits stdio handles so
``capture_output=True`` works. ``DETACHED_PROCESS`` would sever
stdio and break stdout capture.

``CREATE_BREAKAWAY_FROM_JOB`` is included so that child processes
spawned inside an Electron / Tauri Windows Job Object are not
forcibly re-parented into the job β€” without this flag,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CREATE_BREAKAWAY_FROM_JOB can be denied by restrictive Job Objects; current main documents that at lines 138-144 and only retries detached spawns without this bit. Adding it here makes every short-lived hidden spawn subject to that failure with no fallback. Please first reproduce the actual flashing spawn on native Windows and scope any breakaway behavior to that path.

``CREATE_NO_WINDOW`` is silently ignored and a visible ``cmd.exe``
window flashes for every subprocess.
"""
if not IS_WINDOWS:
return 0
return _CREATE_NO_WINDOW
return _CREATE_NO_WINDOW | _CREATE_BREAKAWAY_FROM_JOB


def windows_detach_popen_kwargs() -> dict:
Expand Down
23 changes: 23 additions & 0 deletions tests/tools/test_windows_native_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,29 @@ def test_windows_detach_flags_includes_breakaway_from_job(self, monkeypatch):
"can respawn the gateway after Electron exits."
)

def test_windows_hide_flags_includes_breakaway_from_job(self, monkeypatch):
"""CREATE_BREAKAWAY_FROM_JOB is load-bearing for ``windows_hide_flags()``.

Without it, ``CREATE_NO_WINDOW`` alone is silently ignored when the
parent process lives inside a Windows Job Object (Electron, Tauri,
etc.). Every subprocess spawn produces a visible ``cmd.exe`` flash
instead of running headless.

Regression guard for issue #55604.
"""
from hermes_cli import _subprocess_compat as sc
monkeypatch.setattr(sc, "IS_WINDOWS", True)
flags = sc.windows_hide_flags()
assert flags & 0x08000000, "missing CREATE_NO_WINDOW"
assert flags & 0x01000000, (
"CREATE_BREAKAWAY_FROM_JOB (0x01000000) must be present in "
"windows_hide_flags() so child processes inside Electron / Tauri "
"job objects are not forcibly re-parented."
)
# Must NOT include DETACHED_PROCESS β€” that severs stdio and breaks
# capture_output=True, which is the whole point of hide vs detach.
assert not (flags & 0x00000008), "DETACHED_PROCESS must not be in hide flags"

def test_windows_detach_flags_without_breakaway_drops_only_that_bit(
self, monkeypatch
):
Expand Down
Loading