hermes - ✅(Solved) Fix [Bug]: macOS launchd plist registers Login Item as "python" instead of "hermes" [2 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
NousResearch/hermes-agent#15636Fetched 2026-04-26 05:26:01
View on GitHub
Comments
0
Participants
1
Timeline
6
Reactions
0
Author
Participants
Timeline (top)
labeled ×4cross-referenced ×2

Root Cause

hermes_cli/gateway.py::generate_launchd_plist() hard-codes ProgramArguments to invoke the venv Python with -m hermes_cli.main:

# hermes_cli/gateway.py:1838-1851
prog_args = [
    f"<string>{python_path}</string>",
    "<string>-m</string>",
    "<string>hermes_cli.main</string>",
]
prog_args.extend([
    "<string>gateway</string>",
    "<string>run</string>",
    "<string>--replace</string>",
])

macOS Background Task Management uses basename(ProgramArguments[0]) as the display name for Login Items. Because ProgramArguments[0] is …/venv/bin/python, the entry is attributed to "python".

This is documented as the wrong pattern by macOS admin guides. From Manage and enforce custom Login and Background items in macOS Ventura (macblog.org):

I've seen some launchd configurations that use the ProgramArguments array to first specify the path to an interpreter, then the path to a script… That's wrong, and it causes macOS to attribute the interpreter as the launched process.

Don't do this. You should set the path to the script's interpreter in its shebang, then make the script itself executable.

Notably, Hermes already uses the correct pattern in its own ecosystem — the reference plist proposed for the hindsight memory plugin in #8973 invokes a named entry-point binary as ProgramArguments[0]:

<string>${HERMES_VENV}/bin/hindsight-embed</string>

…which would correctly display as "hindsight-embed" in System Settings. The gateway plist is the asymmetric outlier.

Fix Action

Fixed

PR fix notes

PR #15640: fix(gateway): macOS launchd plist registers Login Item as "hermes" not "python"

Description (problem / solution / changelog)

What

Make generate_launchd_plist() produce a plist whose ProgramArguments[0] is the venv's hermes console script instead of …/venv/bin/python -m hermes_cli.main. Falls back to the previous python -m hermes_cli.main form when the console script is missing.

Why

macOS Background Task Management uses basename(ProgramArguments[0]) as the display name in System Settings → General → Login Items & Extensions and in the system notification banner that fires after hermes gateway install:

"python" is an item that can run in the background. You can manage this in Login Items & Extensions.

Users cannot tell which background item belongs to Hermes — especially when other Python-based launch agents are installed — and disabling the entry from System Settings is opaque. With this change, macOS displays the entry as "hermes", which matches the binary the user invoked, the plist Label, and the pattern already used elsewhere in the project (the proposed hindsight memory plugin plist in #8973 invokes ${HERMES_VENV}/bin/hindsight-embed for the same reason).

The runtime behavior is identical: <venv>/bin/hermes is a Python entry-point script with a shebang pointing at the same <venv>/bin/python3, so the same interpreter loads hermes_cli.main either way.

This pattern is the documented best practice; see macblog.org — Manage and enforce custom Login and Background items in macOS Ventura:

I've seen some launchd configurations that use the ProgramArguments array to first specify the path to an interpreter, then the path to a script… That's wrong, and it causes macOS to attribute the interpreter as the launched process.

Don't do this. You should set the path to the script's interpreter in its shebang, then make the script itself executable.

Fixes #15636.

How to test

Automated

pytest tests/hermes_cli/test_gateway_service.py -k launchd_plist -v

Two new tests cover both branches:

  • test_launchd_plist_uses_hermes_console_script_when_present — when <venv>/bin/hermes exists, ProgramArguments[0] is the script and there is no python -m hermes_cli.main invocation.
  • test_launchd_plist_falls_back_to_python_module_when_console_script_missing — when the console script is absent, the original python -m hermes_cli.main form is preserved.

Existing test_launchd_plist_includes_profile continues to pass (profile arg still threaded through ProgramArguments).

Manual (macOS)

hermes gateway uninstall          # if previously installed
hermes gateway install
hermes gateway status

Expected:

  • Notification banner reads "hermes is an item that can run in the background."
  • System Settings → General → Login Items & Extensions lists the entry as hermes.
  • hermes gateway status reports ✓ Service installed and loaded and does not report "Service definition is stale".
  • Telegram/Discord/cron functionality unchanged.

Platforms tested

  • macOS 15.7.1 (Sequoia, Apple Silicon), Python 3.11.14 in venv. Telegram gateway and cron jobs verified working post-install.
  • Linux/systemd path is untouched by this change (the fix is scoped to generate_launchd_plist()).

Related issues

  • Fixes #15636 — the bug report this PR addresses.
  • Aligns with #8973 — the hindsight plist already uses the named-binary pattern proposed here.
  • Narrower than #6350 — that issue requests fully customizable scripts; this PR doesn't require that, just the correct binary name.
  • Compatible with #8125 — launchd_plist_is_current() will continue to detect drift correctly because both the generator and the comparison use the same fresh output.

Changed files

  • hermes_cli/gateway.py (modified, +17/-6)
  • tests/hermes_cli/test_gateway_service.py (modified, +48/-0)

PR #15885: fix(gateway): use venv/bin/hermes in launchd ProgramArguments so Login Item shows "hermes" (#15636)

Description (problem / solution / changelog)

Summary

  • generate_launchd_plist() switched from python -m hermes_cli.main to venv/bin/hermes as ProgramArguments[0]
  • macOS now shows hermes (not python) in System Settings → Login Items & Extensions
  • Removes the now-unused python_path local variable

The bug

macOS derives the Login Item display name from ProgramArguments[0]. The old plist started with the Python interpreter path (/path/to/venv/bin/python3), so the notification banner and System Settings listed the agent as python — indistinguishable from any other Python script running as a Login Item.

The fix

venv/bin/hermes is the setuptools console-script entry-point installed by pip install -e .. Its shebang is the same venv Python; at runtime it calls hermes_cli.main:main() identically to the old -m hermes_cli.main form. The existing PATH, VIRTUAL_ENV, and HERMES_HOME environment variables in the plist already ensure the correct virtualenv is active.

Existing installations with the old plist will have launchd_plist_is_current() return False on next gateway start, triggering refresh_launchd_plist_if_needed() to update the on-disk plist automatically.

Test plan

  • Before: test_plist_uses_hermes_binary_not_python fails — old ProgramArguments[0] is the Python interpreter path, endswith("/hermes") is False; -m and hermes_cli.main appear in the array
  • After: 42/42 tests in test_update_gateway_restart.py pass
  • Regression guard: reverted the fix → observed AssertionError: Expected first ProgramArgument to end with '/hermes', got: '/path/to/venv/bin/python3'; restored → 0 failures
  • Baseline verified: test_systemd_start_refreshes_outdated_unit fails on clean origin/main (macOS: no D-Bus/systemd); unrelated to this change
  • Adjacent suite: tests/hermes_cli/ — 1061 passed, 2 skipped, 1 pre-existing baseline, 0 new failures

Related

  • Fixes #15636

🤖 Generated with Claude Code

Changed files

  • hermes_cli/gateway.py (modified, +4/-4)
  • tests/hermes_cli/test_update_gateway_restart.py (modified, +36/-0)

Code Example

# hermes_cli/gateway.py:1838-1851
prog_args = [
    f"<string>{python_path}</string>",
    "<string>-m</string>",
    "<string>hermes_cli.main</string>",
]
prog_args.extend([
    "<string>gateway</string>",
    "<string>run</string>",
    "<string>--replace</string>",
])

---

<string>${HERMES_VENV}/bin/hindsight-embed</string>

---

-    prog_args = [
-        f"<string>{python_path}</string>",
-        "<string>-m</string>",
-        "<string>hermes_cli.main</string>",
-    ]
+    venv_dir = _detect_venv_dir() or (PROJECT_ROOT / "venv")
+    hermes_bin = venv_dir / "bin" / "hermes"
+    prog_args = [
+        f"<string>{hermes_bin}</string>",
+    ]
     if profile_arg:
         for part in profile_arg.split():
             prog_args.append(f"<string>{part}</string>")
     prog_args.extend([
         "<string>gateway</string>",
         "<string>run</string>",
         "<string>--replace</string>",
     ])
RAW_BUFFERClick to expand / collapse

Bug Description

On macOS Ventura+, after hermes gateway install, the system shows a notification:

"python" is an item that can run in the background. You can manage this in Login Items & Extensions.

…and System Settings → General → Login Items & Extensions → Allow in the Background lists the entry as python, not hermes. The attribution is incorrect: users cannot tell which background item belongs to Hermes (especially when other Python-based launch agents are installed), and disabling it from System Settings is opaque.

Steps to Reproduce

  1. Fresh macOS install (Ventura, Sonoma, or Sequoia).
  2. Install Hermes per the Quickstart.
  3. Run hermes gateway install.
  4. Observe the macOS Notification Center banner.
  5. Open System Settings → General → Login Items & Extensions and look under "Allow in the Background".

Expected Behavior

The Login Item is registered as hermes (matches the binary the user invoked, matches the Label in the plist, matches existing Hermes-internal helper plists — see Root Cause below).

Actual Behavior

The Login Item is registered as python because that's the basename of ProgramArguments[0] in the generated plist.

Affected Component

  • Gateway (Telegram/Discord/Slack/WhatsApp)
  • Setup / Installation

Messaging Platform

N/A — affects all platforms (it's a service-installer issue, not a platform adapter issue).

Operating System

macOS 15.7.1 (Sequoia, Apple Silicon). Reproduces on Ventura 13.x and Sonoma 14.x as well — the underlying macOS Background Task Management behavior is unchanged since 13.0.

Python Version

3.11.14 (in venv)

Hermes Version

v0.10.0 (2026.4.16)

Root Cause Analysis

hermes_cli/gateway.py::generate_launchd_plist() hard-codes ProgramArguments to invoke the venv Python with -m hermes_cli.main:

# hermes_cli/gateway.py:1838-1851
prog_args = [
    f"<string>{python_path}</string>",
    "<string>-m</string>",
    "<string>hermes_cli.main</string>",
]
prog_args.extend([
    "<string>gateway</string>",
    "<string>run</string>",
    "<string>--replace</string>",
])

macOS Background Task Management uses basename(ProgramArguments[0]) as the display name for Login Items. Because ProgramArguments[0] is …/venv/bin/python, the entry is attributed to "python".

This is documented as the wrong pattern by macOS admin guides. From Manage and enforce custom Login and Background items in macOS Ventura (macblog.org):

I've seen some launchd configurations that use the ProgramArguments array to first specify the path to an interpreter, then the path to a script… That's wrong, and it causes macOS to attribute the interpreter as the launched process.

Don't do this. You should set the path to the script's interpreter in its shebang, then make the script itself executable.

Notably, Hermes already uses the correct pattern in its own ecosystem — the reference plist proposed for the hindsight memory plugin in #8973 invokes a named entry-point binary as ProgramArguments[0]:

<string>${HERMES_VENV}/bin/hindsight-embed</string>

…which would correctly display as "hindsight-embed" in System Settings. The gateway plist is the asymmetric outlier.

Proposed Fix

Change generate_launchd_plist() in hermes_cli/gateway.py (around line 1838) to invoke the venv hermes entry-point script directly. The venv ships …/venv/bin/hermes (a Python entry-point script with a shebang pointing at the same venv python3), so the runtime behavior is identical:

-    prog_args = [
-        f"<string>{python_path}</string>",
-        "<string>-m</string>",
-        "<string>hermes_cli.main</string>",
-    ]
+    venv_dir = _detect_venv_dir() or (PROJECT_ROOT / "venv")
+    hermes_bin = venv_dir / "bin" / "hermes"
+    prog_args = [
+        f"<string>{hermes_bin}</string>",
+    ]
     if profile_arg:
         for part in profile_arg.split():
             prog_args.append(f"<string>{part}</string>")
     prog_args.extend([
         "<string>gateway</string>",
         "<string>run</string>",
         "<string>--replace</string>",
     ])

After this change:

  • macOS displays the Login Item as hermes
  • Notification banner reads "hermes" is an item that can run in the background ✓
  • hermes gateway status no longer reports "Service definition is stale" against a manually-corrected plist (relevant to users who already worked around this — me included)
  • No change to any runtime behavior — the same Python interpreter runs the same hermes_cli.main entry point

I verified this manually by editing ~/Library/LaunchAgents/ai.hermes.gateway.plist to use venv/bin/hermes and reloading via launchctl. The gateway runs identically (Telegram connected, cron jobs intact); the Login Items entry is now correctly named.

Related issues

  • #6350 — feature request to stop hard-coding the launchd plist (broader; this issue is a narrower, more concrete subset that doesn't require introducing user-customizable scripts).
  • #8973 — already adopts the correct ${VENV}/bin/<entry-point> pattern for the hindsight plist; this issue would align the gateway plist with it.
  • #8125 — launchd_plist_is_current() self-heal will currently overwrite manual user fixes; another reason this should be fixed upstream rather than worked around in user installs.

Are you willing to submit a PR for this?

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

extent analysis

TL;DR

Change the generate_launchd_plist() function in hermes_cli/gateway.py to invoke the venv hermes entry-point script directly instead of using the Python interpreter.

Guidance

  • Update the prog_args list in generate_launchd_plist() to use the hermes entry-point script: replace python_path with the path to the hermes script in the venv.
  • Verify the change by checking the Login Items in System Settings and ensuring the entry is named "hermes".
  • Test the gateway functionality to ensure no runtime behavior changes.
  • Consider reviewing related issues (#6350, #8973, #8125) for broader context and potential further improvements.

Example

venv_dir = _detect_venv_dir() or (PROJECT_ROOT / "venv")
hermes_bin = venv_dir / "bin" / "hermes"
prog_args = [
    f"<string>{hermes_bin}</string>",
]

Notes

This fix assumes the hermes entry-point script is correctly configured and functional in the venv. If issues persist, further debugging may be necessary.

Recommendation

Apply the proposed fix by changing the generate_launchd_plist() function to use the hermes entry-point script, as this correctly attributes the Login Item and aligns with macOS best practices.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING