Summary
hermes gateway status detects a stale launchd service definition and advises running
hermes gateway start. Following that advice silently discards every operator
customisation in the installed plist — no warning, no diff, no backup. The tool
recommends the action that destroys the configuration.
Observed on v0.20.0 (2026.8.3), macOS 26.5.2.
What gets dropped
hermes_cli/gateway.py builds the plist from a hardcoded f-string template. It emits
exactly three environment variables and no resource limits:
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key> <string>{sane_path}</string>
<key>VIRTUAL_ENV</key> <string>{venv_dir}</string>
<key>HERMES_HOME</key> <string>{hermes_home}</string>
</dict>
There is no SoftResourceLimits / HardResourceLimits emission anywhere in the file, and
the env dict has no merge step. So regeneration drops:
- any
*ResourceLimits — e.g. a raised NumberOfFiles, added because the gateway
exhausts the default descriptor limit under load
- any additional
EnvironmentVariables — e.g. a daemon idle-timeout tuning
On our host both were deliberate prior fixes with dated backups. Regenerating would have
reverted a limit raise and a memory-daemon setting that had been pinned an hour earlier
— while the tool reported it as routine maintenance.
Why it is worse than a plain overwrite
- The tool recommends it.
status surfaces the stale-definition warning and names
start as the remedy, so the destructive path is the advised one.
- It is silent. No diff of installed-vs-generated, no "the following keys will be
dropped", no backup of the existing plist.
- The loss is invisible afterwards. A regenerated plist is perfectly well-formed. The
only way to notice is to already know what used to be there. Resource-limit regressions
in particular resurface later as unrelated-looking failures under load.
restart is safe and start is not, which is not discoverable from the help text.
refresh_launchd_plist_if_needed is called only from start and install.
Reproduction
- Add any customisation to the installed plist — e.g.
SoftResourceLimits/NumberOfFiles = 4096, or an extra EnvironmentVariables entry.
- Cause the service definition to be considered stale (e.g. upgrade).
hermes gateway status → reports the definition as stale, advises hermes gateway start.
- Run it. The plist is regenerated; the customisation is gone, unmentioned.
Suggested fixes, cheapest first
- Back up before regenerating. Write the existing plist to a timestamped sibling. One
line, and it makes the loss recoverable rather than terminal.
- Diff and confirm. Before overwriting, compare installed vs generated and print the
keys about to be dropped; require confirmation (or --force) when the set is non-empty.
- Merge rather than replace. Preserve unrecognised top-level keys and unrecognised
EnvironmentVariables entries from the installed plist. This is the real fix: it makes
the operation idempotent for operators who have tuned anything.
- Correct the recommendation. Where
status currently suggests start, suggest
restart when the gateway is already running — it refreshes the process without
touching the plist.
(1) and (4) alone would remove the trap. (3) is what makes start safe to recommend at all.
Note
Happy to send a PR for the backup-plus-diff variant if that shape is acceptable — it is
small and does not change the default behaviour beyond adding the safety net.
Summary
hermes gateway statusdetects a stale launchd service definition and advises runninghermes gateway start. Following that advice silently discards every operatorcustomisation in the installed plist — no warning, no diff, no backup. The tool
recommends the action that destroys the configuration.
Observed on v0.20.0 (2026.8.3), macOS 26.5.2.
What gets dropped
hermes_cli/gateway.pybuilds the plist from a hardcoded f-string template. It emitsexactly three environment variables and no resource limits:
There is no
SoftResourceLimits/HardResourceLimitsemission anywhere in the file, andthe env dict has no merge step. So regeneration drops:
*ResourceLimits— e.g. a raisedNumberOfFiles, added because the gatewayexhausts the default descriptor limit under load
EnvironmentVariables— e.g. a daemon idle-timeout tuningOn our host both were deliberate prior fixes with dated backups. Regenerating would have
reverted a limit raise and a memory-daemon setting that had been pinned an hour earlier
— while the tool reported it as routine maintenance.
Why it is worse than a plain overwrite
statussurfaces the stale-definition warning and namesstartas the remedy, so the destructive path is the advised one.dropped", no backup of the existing plist.
only way to notice is to already know what used to be there. Resource-limit regressions
in particular resurface later as unrelated-looking failures under load.
restartis safe andstartis not, which is not discoverable from the help text.refresh_launchd_plist_if_neededis called only fromstartandinstall.Reproduction
SoftResourceLimits/NumberOfFiles = 4096, or an extraEnvironmentVariablesentry.hermes gateway status→ reports the definition as stale, adviseshermes gateway start.Suggested fixes, cheapest first
line, and it makes the loss recoverable rather than terminal.
keys about to be dropped; require confirmation (or
--force) when the set is non-empty.EnvironmentVariablesentries from the installed plist. This is the real fix: it makesthe operation idempotent for operators who have tuned anything.
statuscurrently suggestsstart, suggestrestartwhen the gateway is already running — it refreshes the process withouttouching the plist.
(1) and (4) alone would remove the trap. (3) is what makes
startsafe to recommend at all.Note
Happy to send a PR for the backup-plus-diff variant if that shape is acceptable — it is
small and does not change the default behaviour beyond adding the safety net.