fix(cli): warn when hermes runs unsupervised as PID 1

A deployment that overrides the image's `entrypoint:` to invoke hermes
directly skips docker/entrypoint-dispatch.sh entirely, so hermes itself
becomes PID 1 with no s6-overlay /init (or any other init) above it.
Nothing then reaps orphaned grandchildren (browser tooling, MCP
subprocesses, shell-tool children) reparented to PID 1, and they
accumulate as zombies without bound.

entrypoint-dispatch.sh already warns on its own non-PID-1 fallback
path, but that script never runs in the entrypoint-override case, so
there was no signal at all. Add the same style of warning inside
hermes_cli.main, gated on being PID 1 on Linux, pointing users at the
image's default ENTRYPOINT or `docker run --init` / `init: true`.

Fixes #111577
This commit is contained in:
chelsealong
2026-09-15 05:46:19 +00:00
committed by Teknium
parent 54ed7cbb7b
commit 8d5cce4d94
2 changed files with 66 additions and 0 deletions
+33
View File
@@ -183,6 +183,38 @@ def _run_and_exit_oneshot(
_exit_after_oneshot(rc)
def _warn_if_unsupervised_pid1(pid: "int | None" = None) -> None:
"""Warn when this process is PID 1 with nothing above it to reap orphans.
Docker/Podman normally run the image's own supervisor
(``docker/entrypoint-dispatch.sh`` -> s6-overlay's ``/init``) as PID 1,
which reaps orphaned grandchildren reparented to it. A deployment that
overrides ``entrypoint:`` to invoke hermes directly skips that dispatcher
entirely, so hermes itself becomes PID 1: nothing calls ``wait()`` on
orphaned children (browser tooling, MCP subprocesses, shell-tool
children), and they accumulate as zombies without bound. See
NousResearch/hermes-agent#111577. This mirrors the warning
entrypoint-dispatch.sh already prints on its own non-PID-1 fallback path.
"""
try:
import platform
if platform.system() != "Linux":
return
if (pid if pid is not None else os.getpid()) != 1:
return
print(
"[hermes] WARNING: this process is PID 1 with no init above it "
"(entrypoint override?). Orphaned child processes will not be "
"reaped and will accumulate as zombies. Use the image's default "
"ENTRYPOINT (docker/entrypoint-dispatch.sh) instead of overriding "
"it, or run with `docker run --init` / `init: true` in Compose.",
file=sys.stderr,
)
except Exception:
pass
def _set_process_title() -> None:
"""Cosmetic: show 'hermes' instead of 'python3.xx' in ps/top/htop.
@@ -3396,6 +3428,7 @@ def _default_to_chat(args) -> None:
def main():
"""Main entry point for hermes CLI."""
_set_process_title()
_warn_if_unsupervised_pid1()
_advertise_agent_env()
# Force UTF-8 stdio on Windows before anything prints. No-op elsewhere.