Skip to content

hermes gateway install silently resets HERMES_HOME on macOS LaunchAgent #32741

@dtmsyi

Description

@dtmsyi

hermes gateway install silently resets HERMES_HOME on macOS LaunchAgent

Summary

On macOS, running hermes gateway install rewrites ~/Library/LaunchAgents/ai.hermes.gateway.plist from a hard-coded template that ignores the current HERMES_HOME of the running CLI environment. The previous HERMES_HOME value (and the HERMES_HOME-derived StandardOutPath / StandardErrorPath) are silently overwritten with defaults. There is no flag to pin them.

This bit us in production: a Slack-ingesting gateway pinned to ~/.hermes-bake-off/ was reinstalled via hermes gateway install to apply an unrelated config change. The install reset HERMES_HOME to ~/.hermes/ (default), the agent restarted reading the wrong tree, and Slack ingest broke for ~5 minutes until we noticed and manually re-pinned the plist.

Environment

  • macOS (Apple Silicon; expect Intel behaves the same)
  • Hermes CLI: latest as of 2026-05-26
  • LaunchAgent label: ai.hermes.gateway

Reproduction

# Set up a non-default HERMES_HOME tree
mkdir -p ~/.hermes-custom
HERMES_HOME=~/.hermes-custom hermes gateway install

# Verify the plist points at the custom tree
/usr/libexec/PlistBuddy -c 'Print :EnvironmentVariables:HERMES_HOME' \
  ~/Library/LaunchAgents/ai.hermes.gateway.plist
# → /Users/<you>/.hermes-custom    ✓

# Time passes. Now run install again (no env override this time)
hermes gateway install

# Plist is silently rewritten:
/usr/libexec/PlistBuddy -c 'Print :EnvironmentVariables:HERMES_HOME' \
  ~/Library/LaunchAgents/ai.hermes.gateway.plist
# → /Users/<you>/.hermes           ✗ (silently reset; no warning)

Expected behavior

One of:

  1. Read current plist's HERMES_HOME first and preserve it across reinstall.
  2. Accept a --hermes-home <path> flag so callers can pin explicitly. (Current help shows only --force, --system, --run-as-user.)
  3. Honor $HERMES_HOME from the calling shell when generating the plist (current behavior appears to use a hard-coded default regardless of env).
  4. At minimum, emit a warning to stderr when the new plist's HERMES_HOME differs from the previous one.

Affected fields

The install templates all four fields off the default ~/.hermes root:

Field Default after install Custom value silently lost
EnvironmentVariables.HERMES_HOME ~/.hermes ~/.hermes-custom
StandardOutPath ~/.hermes/logs/gateway.log ~/.hermes-custom/logs/gateway.log
StandardErrorPath ~/.hermes/logs/gateway.error.log ~/.hermes-custom/logs/gateway.error.log
EnvironmentVariables.PATH trims trailing entries beyond a default set custom PATH tail (e.g., claude-plugin caches) silently stripped

Our workaround

Shipped a local wrapper that wraps hermes gateway install:

  1. Captures the four fields above via PlistBuddy before install.
  2. Backs up the plist with a timestamp suffix.
  3. Runs hermes gateway install "$@" (passes through --force etc.).
  4. Re-patches the four fields via PlistBuddy.
  5. Validates via plutil -lint.
  6. launchctl bootout + bootstrap to apply.
  7. Verifies the agent reaches state = running within 10s; restores backup on any failure.

Source: ~/.claude/helpers/hermes-gateway-install-safe.sh (private; happy to share if useful).

Why this matters

Anyone running a non-default Hermes home (parallel gateways, bake-off setups, per-org isolation, staging vs prod on one Mac) currently can't safely reinstall the gateway without manual plist forensics. The failure mode is silent — the agent comes back up reading the wrong tree, and depending on what's in ~/.hermes/ vs ~/.hermes-custom/ you may not notice until something visible breaks (in our case Slack ingest).

A one-line preservation in the macOS install path would close this.

Happy to send a PR if the maintainers can confirm which of the four behaviors above is preferred.

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