Describe the bug
When running hermes gateway setup to configure a platform (e.g. Telegram) on macOS, the setup wizard offers to restart the gateway to pick up changes. If the gateway was running but the launchd plist was never installed (or was deleted), the restart fails with:
⚠ Gateway drain timed out after 180s — forcing launchd restart
Could not find service "ai.hermes.gateway" in domain for user gui: 501
↻ launchd job was unloaded; reloading
Bootstrap failed: 5: Input/output error
✗ Restart failed
Root cause
launchd_restart() in hermes_cli/gateway.py has a fallback path (line ~3054) that handles the case where the launchd job is not loaded. It attempts to launchctl bootstrap the plist, but never checks whether the plist file actually exists on disk first. If the plist is missing, bootstrap fails with error 5 (EIO).
This happens when:
1. User runs hermes gateway setup and configures a platform
2. The gateway is running (foreground or previously installed) but the plist was never written to ~/Library/LaunchAgents/
3. The setup wizard calls launchd_restart() which tries launchctl kickstart -k, killing the gateway process
4. The fallback bootstrap fails because no plist exists at the expected path
Expected behavior
launchd_restart() should self-heal a missing plist, the same way launchd_start() already does (line ~2943):
python
if not plist_path.exists():
print("↻ launchd plist missing; regenerating service definition")
plist_path.parent.mkdir(parents=True, exist_ok=True)
plist_path.write_text(generate_launchd_plist(), encoding="utf-8")
Environment
- macOS 15.6.1
- Hermes Agent (installed via git)
- launchd service management
Workaround
Manually run hermes gateway install before restarting, which creates the plist.
Describe the bug
Root cause
Expected behavior
Workaround