fix(gateway): launchd identity requires darwin, like control_socket

This commit is contained in:
kshitijk4poor
2026-09-20 18:01:00 +05:30
committed by kshitij
parent 3dc170e58e
commit 23bd1ee632
2 changed files with 13 additions and 10 deletions

View File

@@ -4,6 +4,7 @@ import math
import os
import re
import subprocess
import sys
from collections.abc import Callable, Mapping
from hermes_cli.config import DEFAULT_CONFIG
@@ -93,6 +94,8 @@ def launchd_service_label(environ: Mapping[str, str] | None = None) -> str | Non
``exit timeout = 1`` for them — treating those as a budget would cap the
drain to 0 for a gateway Ctrl+C'd in such a terminal.
"""
if sys.platform != "darwin":
return None
env = os.environ if environ is None else environ
label = str(env.get("XPC_SERVICE_NAME", "") or "").strip()
if not label.startswith("ai.hermes"):
@@ -110,7 +113,7 @@ def read_launchd_exit_timeout_s(
"""Live ``ExitTimeOut`` (seconds) launchd enforces for this gateway's job.
Returns ``None`` — meaning "no launchd budget applies" — when the process
is not launchd-owned (no ``XPC_SERVICE_NAME``), ``launchctl`` is missing
is not launchd-owned (non-darwin, or no ``ai.hermes`` ``XPC_SERVICE_NAME``), ``launchctl`` is missing
or fails, or the print output carries no ``exit timeout`` line. Callers
must treat ``None`` as fail-open: the configured drain stands unchanged.
"""
@@ -118,11 +121,7 @@ def read_launchd_exit_timeout_s(
if not label:
return None
if uid is None:
# launchd is macOS-only; Windows has no os.getuid, so resolve it via getattr.
_getuid = getattr(os, "getuid", None)
if _getuid is None:
return None
uid = _getuid()
uid = os.getuid() # only reachable on darwin: launchd_service_label() is None elsewhere
domain = "system" if uid == 0 else f"gui/{uid}"
try:
proc = run(

View File

@@ -17,6 +17,7 @@ from types import SimpleNamespace
import pytest
import gateway.restart as restart_mod
from gateway.restart import (
LAUNCHD_STOP_CLEANUP_RESERVE_S,
effective_stop_drain_timeout,
@@ -28,14 +29,17 @@ from gateway.shutdown_watchdog import resolve_shutdown_watchdog_delay
@pytest.mark.parametrize(
"label, expected",
"platform, label, expected",
[
("ai.hermes.gateway", 60.0 - LAUNCHD_STOP_CLEANUP_RESERVE_S),
("darwin", "ai.hermes.gateway", 60.0 - LAUNCHD_STOP_CLEANUP_RESERVE_S),
# App-coalition label (IDE integrated terminal) is not our job: no budget, drain unchanged.
("application.com.example.ide.123", 180.0),
("darwin", "application.com.example.ide.123", 180.0),
# launchd is darwin-only (same predicate as control_socket): a leaked label elsewhere is ignored.
("linux", "ai.hermes.gateway", 180.0),
],
)
def test_capped_drain_fits_inside_launchd_budget_minus_reserve(label, expected):
def test_capped_drain_fits_inside_launchd_budget_minus_reserve(monkeypatch, platform, label, expected):
monkeypatch.setattr(restart_mod.sys, "platform", platform)
# The incident shape: configured 180s, launchd clamps to 60s.
assert resolve_launchd_capped_drain(180.0, 60.0) == 60.0 - LAUNCHD_STOP_CLEANUP_RESERVE_S
# Never extends a short drain; no launchd budget leaves the configured drain alone.