chore(process): the supervisor refuses a malformed parent pgid; two comments match the code again

- process_supervisor.sh compares every register line with its parent pgid. An empty or non-numeric
  value made each comparison error out and `|| continue` dropped every registration silently; it
  now exits 2 with a message. (Python always passes os.getpgid(0), so this is robustness, not a
  live bug.) Test: the script refuses "" and "12x".
- The ddgs worker's detached-spawn marker said the worker exits on owner death because it blocks
  on stdin. It does not: communicate() writes the request and closes stdin at once, so a worker
  whose owner dies mid-search finishes that search. The marker now says so.
- mcp_tool_lifecycle's child-filter comment said LSP servers and the slash worker "use
  start_new_session"; they are owned children under the supervisor now.
This commit is contained in:
kshitijk4poor authored and kshitij committed 2026-10-29 16:58:09 +05:30
1 parent 57a6be555e
commit edbb589c60
4 files changed
+14 -3

No files matched your search

+2
View File
@@ -22,6 +22,8 @@
trap '' INT HUP # a dying parent's SIGINT/SIGHUP must not preempt the reap: EOF is the only signal
parent=$1
# Every register line compares against it: a bad value would silently refuse every registration.
case $parent in ''|*[!0-9]*) echo "hermes process supervisor: bad parent pgid '$parent'; refusing to run" >&2; exit 2 ;; esac
# Run inside the parent's group, a killpg of the parent would take us down before we reap.
kill -0 -$$ 2>/dev/null || {
echo "hermes process supervisor: not a process-group leader; refusing to run" >&2
+1 -1
View File
@@ -110,7 +110,7 @@ def _spawn_worker(env: dict[str, str]) -> subprocess.Popen:
env["PYTHONPATH"] = path_entry + os.pathsep + child_pythonpath if child_pythonpath else path_entry
worker_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "_search_worker.py")
# Own process group/session so terminate/kill also reach a hung primp grandchild.
# detached-spawn: ok — awaited under a 30 s cap that kills its group; blocked on its stdin pipe, it exits on owner death
# detached-spawn: ok — awaited under a 30 s cap that kills its group; an owner killed mid-search leaves it to finish that one search
extra_kwargs: dict[str, Any] = {"creationflags": subprocess.CREATE_NEW_PROCESS_GROUP} if sys.platform == "win32" else {"start_new_session": True}
# stdin/stdout/stderr stay explicit keyword args so scripts/check_subprocess_stdin.py sees them
# (TUI gateway inherits stdin). stderr=DEVNULL: a chatty child would deadlock a stdout-only drain.
@@ -816,3 +816,11 @@ def test_no_pgids_is_a_no_op(monkeypatch):
process_identity.own_process_groups([])
assert spawned == []
@pytest.mark.parametrize("parent", ["", "12x"])
def test_the_supervisor_refuses_a_malformed_parent_pgid(parent):
"""Every register line is compared with it, so a bad value would silently refuse them all."""
run = subprocess.run([process_identity._POSIX_SH, str(process_identity._SUPERVISOR_SCRIPT), parent],
input="", capture_output=True, text=True, timeout=10, check=False)
assert run.returncode == 2 and "bad parent pgid" in run.stderr
+3 -2
View File
@@ -78,8 +78,9 @@ def _snapshot_child_pids() -> set:
# argv markers of non-MCP gateway children that can race into the snapshot delta during an
# MCP spawn (defense-in-depth; LSP/slash_worker already use start_new_session). Matched against
# argv[1:] because Python/Java children start with the interpreter path.
# MCP spawn (defense-in-depth: LSP servers and the slash worker lead their own groups under the
# owner-death supervisor, see spawn_owned). Matched against argv[1:] because Python/Java children
# start with the interpreter path.
_NON_MCP_CHILD_CMDLINE_MARKERS: tuple[str, ...] = (
"tui_gateway.slash_worker", "tui_gateway.entry",
"-dorg.eclipse.equinox.launcher", "eclipse.jdt.ls", "org.eclipse.equinox.launcher_", # jdtls