Skip to content

fix(launchd): self-heal missing plist in launchd_restart()#31218

Open
dev-jin wants to merge 1 commit into
NousResearch:mainfrom
dev-jin:fix/launchd-restart-missing-plist
Open

fix(launchd): self-heal missing plist in launchd_restart()#31218
dev-jin wants to merge 1 commit into
NousResearch:mainfrom
dev-jin:fix/launchd-restart-missing-plist

Conversation

@dev-jin

@dev-jin dev-jin commented May 24, 2026

Copy link
Copy Markdown
What does this PR do?

When the launchd plist is missing (e.g. incomplete setup wizard run), launchd_restart() fails with bootstrap failed: 5: Input/output error because it tries to bootstrap a non-existent file.

Adds the same self-heal check already present in launchd_start(): if the plist doesn't exist, regenerate it with generate_launchd_plist() before calling launchctl bootstrap. This mirrors the pattern at line ~2943 in launchd_start().

Related Issue

<!-- No pre-existing issue filed — this PR is the first report. -->

Type of Change

- [x] 🐛 Bug fix (non-breaking change that fixes an issue)

Changes Made

- hermes_cli/gateway.py — Added plist existence check + regeneration in the fallback path of launchd_restart() (6 lines added)

How to Test

1. On macOS, delete the launchd plist: rm ~/Library/LaunchAgents/ai.hermes.gateway.plist
2. Ensure the gateway is running: hermes gateway status
3. Run hermes gateway restart
4. Before fix: fails with Bootstrap failed: 5: Input/output error
5. After fix: prints ↻ launchd plist missing; regenerating service definition and restarts successfully

Checklist

Code

- [x] I've read the Contributing Guide
- [x] My commit messages follow Conventional Commits (fix(launchd): ...)
- [x] I searched for existing PRs to make sure this isn't a duplicate
- [x] My PR contains only changes related to this fix
- [x] I've run pytest tests/ -q and all tests pass (13/13 launchd tests pass)
- [ ] I've added tests for my changes (covered by existing launchd tests)
- [x] I've tested on my platform: macOS 15.6.1

Documentation & Housekeeping

- [x] N/A — no config keys, docs, or architecture changes
- [x] Considered cross-platform impact: change is macOS-only (launchd_restart()), no effect on Linux/Windows

Screenshots /Logs

Before fix:

⚠ 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


After fix:

↻ launchd job was unloaded; reloading
↻ launchd plist missing; regenerating service definition
✓ Service restarted

When the launchd plist is missing (e.g. incomplete setup wizard run),
launchd_restart() fails with 'bootstrap failed: 5: Input/output error'
because it tries to bootstrap a non-existent file.

Add the same self-heal check already present in launchd_start(): if
the plist doesn't exist, regenerate it with generate_launchd_plist()
before calling launchctl bootstrap.

This mirrors the pattern at line ~2943 in launchd_start().
Copilot AI review requested due to automatic review settings May 24, 2026 00:44

Copilot AI 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.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Improves launchd_restart() robustness by regenerating the per-profile LaunchAgent plist when it’s missing, enabling restart to succeed after partial/unloaded setups.

Changes:

  • Add a self-healing step to recreate the LaunchAgents plist when absent.
  • Ensure the LaunchAgents directory exists before bootstrapping.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread hermes_cli/gateway.py
Comment on lines +3057 to +3062
# Self-heal: if the plist was deleted (e.g., incomplete setup wizard
# run) recreate it before bootstrapping — mirrors launchd_start().
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")
Comment thread hermes_cli/gateway.py
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")
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/cli CLI entry point, hermes_cli/, setup wizard labels May 24, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

Fixes #31212. Related launchd plist issues: #11323 (bootstrap exit 5), #13172 (recover unloaded gateways), #5110 (double-start race).

@dev-jin

dev-jin commented May 24, 2026

Copy link
Copy Markdown
Author

Issue 31212 was closed and recreated under #31224, so PR links to that.

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 P2 Medium — degraded but workaround exists type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants