Skip to content

[Bug]: macOS launchd: hermes update does not restart profile gateways #38053

@ayoahha

Description

@ayoahha

Bug Description

On macOS with multiple Hermes profiles installed as launchd services, hermes update appears to restart only the current/default launchd service, not all profile LaunchAgents, despite the update code explicitly saying it should restart all gateways.

The affected machine has several profile LaunchAgents:

  • ai.hermes.gateway
  • ai.hermes.gateway-<profile-a>
  • ai.hermes.gateway-<profile-b>
  • ai.hermes.gateway-<profile-c>
  • ai.hermes.gateway-<profile-d>

All of these profile gateways use the same updated Hermes codebase. After hermes update, each running gateway should be restarted so it picks up the new code.

Steps to Reproduce

  1. On macOS, install multiple Hermes profile gateways as launchd services:
  • default
  • <profile-a>
  • <profile-b>
  • <profile-c>
  • <profile-d>
  1. Confirm the services exist:
    ~/Library/LaunchAgents/ai.hermes.gateway.plist

  2. Confirm multiple gateways are running:
    ps -axo pid,ppid,command | grep -i '[h]ermes' | grep -i gateway

  3. Run:
    hermes update

  4. Check which launchd services / gateway PIDs were restarted.

  5. Observe that the update flow restarts the current/default launchd label but does not enumerate and restart every profile LaunchAgent.

Expected Behavior

hermes update should restart every running gateway that uses the updated shared codebase, including macOS profile LaunchAgents:

  • ai.hermes.gateway
  • ai.hermes.gateway-<profile>

This matches the intent documented in hermes_cli/main.py:
"Auto-restart ALL gateways after update.
The code update (git pull) is shared across all profiles, so every running gateway needs restarting to pick up the new code."

Actual Behavior

On macOS launchd, the update flow appears to restart only the launchd label for the active/current profile via get_launchd_label().

Profile LaunchAgents such as ai.hermes.gateway-sport, ai.hermes.gateway-gaming, ai.hermes.gateway-tiny, and ai.hermes.gateway-wikilibriste are not explicitly enumerated and restarted.

The later manual/non-service gateway cleanup path can exclude launchd-managed PIDs via _get_service_pids(), so profile gateways managed by launchd may be neither restarted through launchd enumeration nor handled as manual gateways.

Result: profile gateways can remain running on pre-update code until manually restarted.

Affected Component

CLI (interactive chat), Gateway (Telegram/Discord/Slack/WhatsApp), Configuration (config.yaml, .env, hermes setup)

Messaging Platform (if gateway-related)

No response

Debug Report

Debug report intentionally not uploaded because it contains local profile names, local filesystem paths, and potentially private conversation/log fragments.

Sanitized environment summary:
Hermes Agent: v0.15.1 (2026.5.29) [e114b31e]
OS: macOS 26.5.1 / Darwin 25.x arm64
Python: 3.11.15
Gateway manager: launchd
Gateway platform: Telegram
Profile layout: default profile plus multiple named profiles under `~/.hermes/profiles/<profile>`
Gateway services: default launchd service plus multiple profile launchd services matching `ai.hermes.gateway-<profile>`

Operating System

macOS 26.5.1 (Build 25F80)

Python Version

3.11.15

Hermes Version

0.15.1

Additional Logs / Traceback (optional)

Observed launchd services:
~/Library/LaunchAgents/ai.hermes.gateway.plist
~/Library/LaunchAgents/ai.hermes.gateway-<profile-a>.plist
~/Library/LaunchAgents/ai.hermes.gateway-<profile-b>.plist
~/Library/LaunchAgents/ai.hermes.gateway-<profile-c>.plist
~/Library/LaunchAgents/ai.hermes.gateway-<profile-d>.plist

Observed running gateway processes:
~/.hermes/hermes-agent/venv/bin/python -m hermes_cli.main gateway run --replace
~/.hermes/hermes-agent/venv/bin/python -m hermes_cli.main --profile <profile-a> gateway run --replace
~/.hermes/hermes-agent/venv/bin/python -m hermes_cli.main --profile <profile-b> gateway run --replace
~/.hermes/hermes-agent/venv/bin/python -m hermes_cli.main --profile <profile-c> gateway run --replace
~/.hermes/hermes-agent/venv/bin/python -m hermes_cli.main --profile <profile-d> gateway run --replace

hermes gateway status reports:
     Other profiles:                                                                                                                                                                                                                                                                        
       ✓ <profile-a><profile-b><profile-c><profile-d>

Root Cause Analysis (optional)

The issue appears to be in hermes_cli/main.py inside the cmd_update gateway restart flow.

The code comment says:
"Auto-restart ALL gateways after update.
The code update (git pull) is shared across all profiles, so every running gateway needs restarting to pick up the new code."

The flow imports:

  • find_gateway_pids(..., all_profiles=True)
  • find_profile_gateway_processes
  • _get_service_pids
  • _graceful_restart_via_sigusr1

So the intended behavior is clearly all-profile restart.

However, the macOS launchd service restart path appears to use get_launchd_label() for the current active profile only, then calls the launchd restart logic for that single label. It does not enumerate all installed/running ai.hermes.gateway- LaunchAgents.

After that, the manual/non-service gateway section excludes PIDs returned by _get_service_pids(). If profile gateways are launchd-managed, they can be excluded from the manual restart path while also not being restarted through launchd enumeration.

This creates a gap specifically for macOS multi-profile launchd setups.

Proposed Fix (optional)

For macOS launchd inside hermes update, enumerate all running/installed Hermes gateway LaunchAgents, not only the current profile label returned by get_launchd_label().

Possible sources:

  • ~/Library/LaunchAgents/ai.hermes.gateway*.plist
  • launchctl list entries matching ai.hermes.gateway
  • Hermes profile registry, resolving each profile's launchd label under that profile context

Then restart each service-managed gateway exactly once.

The restart flow should preserve the existing _get_service_pids() exclusion so manually discovered process cleanup does not kill freshly restarted launchd-managed gateways.

Related issues:

Are you willing to submit a PR for this?

  • I'd like to fix this myself and submit a PR

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Medium — degraded but workaround existscomp/cliCLI entry point, hermes_cli/, setup wizardcomp/gatewayGateway runner, session dispatch, deliverytype/bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions