Skip to content

fix(gateway): report launchd supervision accurately - #67744

Open
americodias wants to merge 2 commits into
NousResearch:mainfrom
americodias:fix/macos-launchd-diagnostics
Open

fix(gateway): report launchd supervision accurately#67744
americodias wants to merge 2 commits into
NousResearch:mainfrom
americodias:fix/macos-launchd-diagnostics

Conversation

@americodias

@americodias americodias commented Jul 19, 2026

Copy link
Copy Markdown

What does this PR do?

Fixes two misleading macOS gateway diagnostics:

  1. hermes gateway status reported a live gateway as “Running manually” when it was actually owned by a system LaunchDaemon using an operator-defined label such as com.example.hermes-gateway.
  2. Shutdown forensics labelled any PID-1 parent as under_systemd=yes, even though PID 1 is launchd on macOS and may be another init/supervisor on Linux.

The status path now correlates discovered gateway PIDs with launchctl list, so externally managed launchd jobs are reported truthfully without suggesting a duplicate service install. Shutdown logs now emit supervisor=systemd|launchd|pid1|none; the legacy structured under_systemd field remains but is true only when explicit systemd markers are present.

Related Issue

No issue filed. I searched open issues and PRs for launchd status manual system service, under_systemd launchd, and the exact "Running manually" launchd symptom; no duplicate was found.

Related but non-duplicative: #65177 changes restart lifecycle ownership. It does not address external launchd labels or shutdown supervisor labelling.

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

  • hermes_cli/gateway.py
    • add optional service-name metadata to the runtime snapshot
    • correlate gateway PIDs with all launchd jobs, not only Hermes' generated label
    • report operator-managed launchd services as supervised and suppress duplicate-install guidance
  • gateway/shutdown_forensics.py
    • distinguish systemd, launchd, generic PID 1, and unsupervised processes
    • emit supervisor=... instead of the misleading under_systemd=yes|no log token
  • Add regression coverage for custom launchd labels, status rendering, and macOS PID-1 classification.

How to Test

  1. Run the focused regression set:
    pytest tests/hermes_cli/test_gateway_service.py::TestLaunchdServiceRecovery \
      tests/hermes_cli/test_gateway_service.py::TestGatewaySystemServiceRouting::test_gateway_status_reports_external_launchd_supervisor \
      tests/gateway/test_shutdown_forensics.py -o 'addopts=' -q
  2. On macOS, run a gateway from a system LaunchDaemon with a non-generated label, then run hermes gateway status from a shell.
  3. Confirm status names the launchd service and does not say “Running manually” or suggest hermes gateway install.
  4. Trigger/format a shutdown context and confirm the log contains supervisor=launchd, not under_systemd=yes.

Verification performed after rebasing onto current upstream main:

  • Focused regression set: 68 passed
  • Broader affected files: 220 passed, 4 deselected (four systemd restart tests invoke Linux user-D-Bus preflight and are incompatible with the macOS host)
  • Ruff focused check: passed
  • Python compile check: passed
  • git diff --check: passed
  • Direct behavior contract probe: passed

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass — focused and affected-file suites were run; full repository suite was not run locally
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: macOS

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — docstrings updated; no user-facing docs change required
  • I've updated cli-config.yaml.example if I added/changed config keys — N/A, no config changes
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — N/A, no architecture/workflow change
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide
  • I've updated tool descriptions/schemas if I changed tool behavior — N/A, no tool schema changes

Screenshots / Logs

Observed before:

✓ Gateway is running (PID: 4242)
  (Running manually, not as a system service)

Observed after:

✓ Gateway is running, supervised by launchd (PID: 4242)
  Service: com.example.hermes-gateway
  (Externally managed launchd service; no duplicate install needed)

Shutdown formatter after:

signal=SIGTERM supervisor=launchd parent_pid=1 ...

@americodias
americodias force-pushed the fix/macos-launchd-diagnostics branch from 35e3afd to 9a3b580 Compare July 19, 2026 23:43
@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have comp/cli CLI entry point, hermes_cli/, setup wizard comp/gateway Gateway runner, session dispatch, delivery labels Jul 19, 2026

@teknium1 teknium1 left a comment

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.

Thanks for the focused gateway diagnostics fix. The two reported premises remain on current main: hermes_cli/gateway.py:7263-7266 reports a discovered PID as manual when no generated plist exists, and gateway/shutdown_forensics.py:134-143 treats any PPID-1 process as systemd.

Problems

  • hermes_cli/gateway.py:1314 adds subprocess.run(..., text=True) without an explicit encoding. scripts/check-windows-footguns.py:328-348 flags that pattern, and commit c89481db5e standardized encoding='utf-8', errors='replace' for production text-mode subprocess calls.

Suggested changes

  • Add encoding="utf-8", errors="replace" to the new launchctl probe, matching the adjacent launchctl call at hermes_cli/gateway.py:1335-1340 on the PR head.

Automated hermes-sweeper review.

Comment thread hermes_cli/gateway.py
result = subprocess.run(
["launchctl", "list"],
capture_output=True,
text=True,

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.

Please add encoding="utf-8", errors="replace" here. scripts/check-windows-footguns.py:328-348 flags subprocess calls using text=True without encoding=, and this would regress the production-wide UTF-8 policy from c89481db5e.

Match the text-mode subprocess convention standardized in c89481d
(encoding='utf-8', errors='replace') flagged by check-windows-footguns.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@americodias

Copy link
Copy Markdown
Author

Addressed in 9e0a5ca — the new launchctl list probe now passes encoding="utf-8", errors="replace", matching the c89481d convention. Ran tests/hermes_cli/test_gateway_service.py + tests/gateway/test_shutdown_forensics.py: no new failures (the 4 pre-existing TestGatewaySystemServiceRouting systemd failures reproduce identically on the untouched PR head on macOS).

@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 30, 2026
@GottZ

GottZ commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

This was generated by AI during triage.

Summary

Twenty-three PRs address or reference four shutdown-forensics clusters: macOS supervisor detection, systemd manager-scope timeout false positives, Windows planned-stop labels, and portable shutdown diagnostics; several also bundle unrelated voice, installer, auxiliary-client, or WhatsApp changes. The focused diffs either constrain or replace the PPID-1 heuristic, select or validate the owning systemd manager, relabel marker-driven stops, or remove the GNU timeout dependency.

Related pull requests

Duplicates

#25511, #25566, and #25687 duplicate #25525; the shutdown-forensics portion of #25570 also overlaps #25525. #36766, #37324, #57901, and #72943 share the LoadState-gate mechanism, while #61017 and #61532 duplicate #54396's cgroup-scope mechanism; #64029 combines both mechanisms. #71617 duplicates #67744. #64515 and #74011 overlap on the Python timeout supervisor, while #74011 additionally adds Darwin-native diagnostics; #67708 is only a test-side shim for that same portability problem.

Suggested consolidation

Author action on #67744: rebase onto main or split out the uniquely salvageable external-label launchd status detection and supervisor taxonomy, retain the now-addressed UTF-8 subprocess fix, and avoid duplicating #25525, the recorded best existing fix for the exact macOS PPID-1 issue. Keep the distinct recorded best-fix paths visible—#37324 for the strict LoadState fallback, #54396 for cgroup-scope selection, #64029 for their combined hardening, and #61615 for Windows planned-stop labels—while closing or leaving closed their explicit duplicates; coordinate #64515/#74011 around one portable supervisor, preserving #74011's Darwin-native diagnostics.

Complex graph

flowchart LR
    classDef open fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
    classDef merged fill:#dcfce7,stroke:#15803d,color:#14532d
    classDef closed fill:#e5e7eb,stroke:#6b7280,color:#1f2937
    classDef unverified fill:#f3f4f6,stroke:#9ca3af,color:#374151
    classDef best stroke-width:3px,stroke:#b45309
    classDef target stroke-width:3px,stroke:#4338ca
    I25508(["issue #25508 (closed)"])
    I25510(["issue #25510 (closed)"])
    subgraph Dup67744 ["PRs duplicating each other"]
        P67744["PR #67744 (open)"]
        P71617["PR #71617 (closed)"]
    end
    P67744 -.->|partial| I25508
    P67744 -.->|partial| I25510
    class I25508 closed
    class I25510 closed
    class P67744 open
    class P71617 closed
    class P67744 target
    click I25508 "https://github.com/NousResearch/hermes-agent/issues/25508"
    click I25510 "https://github.com/NousResearch/hermes-agent/issues/25510"
    click P67744 "https://github.com/NousResearch/hermes-agent/pull/67744"
    click P71617 "https://github.com/NousResearch/hermes-agent/pull/71617"
Loading

Graph: solid arrow = fixes / best fix, dashed arrow = partial or unverified (see edge label); boxed group = PRs duplicating each other; amber border = best fix; indigo border = target; gray node = closed (state tag in the node label).

Cross-PR triage: Reviewed 23 pull requests and 6 issues in this complex. Diffs were read for 20 of 23 PRs (rest unavailable); Assessment working set: 132 kB of PR diffs, 62 kB of issue/PR text, 29 kB of discussion (45 comments), 65 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch.

@andrexibiza

Copy link
Copy Markdown
Contributor

Rebase-on-behalf delivery — this PR's branch was rebased onto current main (9d6c5a920c7).

Rebased branch: andrexibiza:rebase-on-behalf/67744 (commits a0bcfc0d326 + 8664b896681, original authorship preserved)

Conflict resolution (3 files):

  • hermes_cli/gateway.py — merged cleanly (the explicit-encoding launchctl probe + supervision reporting delta applied; 89 additions).
  • tests/gateway/test_shutdown_forensics.py — 2 pure additions from this PR (parent-summary, formatter coverage); took this PR's side; main's 10 tests all preserved (merged file has 32).
  • tests/hermes_cli/test_gateway_service.py — 1 positional conflict (HEAD empty side; this PR adds 8 status/routing tests incl. termux, planned-restart recovery, external-launchd supervisor); took this PR's side; verified 0 main tests lost (merged file 89 vs main's 79 — superset).

To adopt (author):

git fetch https://github.com/andrexibiza/hermes-agent.git rebase-on-behalf/67744
git checkout <your-branch-name>
git reset --hard FETCH_HEAD
git push --force-with-lease origin <your-branch-name>

py_compile verified; test-coverage superset verified (no main regressions). This unblocks the gateway R1 extraction window (hunk gate).

@andrexibiza

Copy link
Copy Markdown
Contributor

Interlock binding — this PR addresses the gateway.py extraction window of EPIC #78647 (shard #78640). Rebase-on-behalf delivery: andrexibiza:rebase-on-behalf/67744.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cli CLI entry point, hermes_cli/, setup wizard comp/gateway Gateway runner, session dispatch, delivery P3 Low — cosmetic, nice to have sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants