Bug Description
fix(gateway): stop loading hermes repo AGENTS.md into gateway sessions (~10k wasted tokens) (#2891) fixed the direct os.getcwd() path in run_agent.py, but the underlying bug can still happen on macOS launchd installs, which are a canonically supported gateway mode.
In the current macOS service setup, the generated launchd plist sets the gateway process WorkingDirectory to the Hermes repo root. That would be fine if it stayed an internal service-manager detail, but cli.py can still be imported indirectly during normal gateway runtime and has an import/config side effect that rewrites terminal.cwd: . to os.getcwd() for the local backend. Under launchd, that becomes the repo path, so TERMINAL_CWD can get flipped to $HOME/.hermes/hermes-agent.
Once that happens, the gateway again loads the repo's top-level AGENTS.md as project context, contrary to the documented messaging behavior (~ by default, or MESSAGING_CWD when set).
This is not a weird/nonstandard launchd setup. It is a supported path bug caused by the interaction between:
- canonical launchd install behavior
- the current default/example local terminal config shape (
terminal.cwd: ".")
- runtime codepaths that can still re-import / reuse CLI config behavior in gateway execution
Steps to Reproduce
- Install the gateway as a macOS launchd service.
- Use the current default/example local terminal config shape:
terminal.backend: local
terminal.cwd: .
- Start the gateway via launchd and send a message that causes a tool-call turn.
- On a tool-call turn, gateway runtime can indirectly import
cli.py via:
run_agent.py::_cap_delegate_task_calls()
tools.delegate_tool::_get_max_concurrent_children()
tools.delegate_tool::_load_config()
from cli import CLI_CONFIG
- Because
cli.py loads config at import time, it resolves terminal.cwd: . to os.getcwd() and writes that back to TERMINAL_CWD.
- Since launchd set the process working directory to the repo root,
TERMINAL_CWD becomes $HOME/.hermes/hermes-agent.
- Subsequent system prompt builds use
TERMINAL_CWD for context discovery and load the repo AGENTS.md again.
Notes:
MESSAGING_CWD is not required to reproduce the underlying bug.
- Setting
MESSAGING_CWD just makes the expected-vs-actual mismatch more obvious.
Expected Behavior
Messaging gateway sessions should use the documented messaging working directory semantics:
- default to
~
- or use
MESSAGING_CWD when configured
The launchd/systemd service process working directory should not leak into the agent's effective project-context cwd.
More precisely: the launchd service may run with WorkingDirectory=$PROJECT_ROOT, but messaging sessions must not treat that as the user/project workspace.
Actual Behavior
On macOS launchd installs, TERMINAL_CWD can be rewritten to the Hermes repo path during normal runtime, causing the gateway to load the repo's AGENTS.md into system prompts again.
Environment
- OS: macOS 26.3.1 (build 25D771280a)
- Version/Commit:
v2026.4.13 / 1af2e18d408a9dcc2c61d6fc1eef5c6667f8e254
- Python version: 3.13.9
- Install mode: launchd service (
ai.hermes.gateway)
Error Output
No exception is required to trigger this; it is a runtime config/context leak.
Additional Context
Relevant code / evidence:
hermes_cli/gateway.py generates launchd service files with WorkingDirectory={PROJECT_ROOT}
cli.py default config includes terminal.cwd = "."
cli-config.yaml.example also includes cwd: "."
cli.py::load_cli_config() rewrites terminal.cwd: . to os.getcwd() for the local backend and exports it to TERMINAL_CWD
run_agent.py::_cap_delegate_task_calls() imports tools.delegate_tool::_get_max_concurrent_children() on tool-call turns
tools.delegate_tool::_load_config() first tries from cli import CLI_CONFIG, which triggers the import/config side effect above
run_agent.py uses TERMINAL_CWD for context file discovery, so once it points at the repo, the repo AGENTS.md is loaded again
gateway/run.py still documents / implements messaging fallback semantics as MESSAGING_CWD or Path.home()
- docs explicitly document macOS launchd support via
hermes gateway install/start/stop/status
Docs that seem inconsistent with the current behavior:
website/docs/user-guide/messaging/index.md documents launchd as a supported gateway mode
website/docs/user-guide/configuration.md says messaging gateway cwd should be ~ by default or MESSAGING_CWD
website/docs/reference/environment-variables.md documents MESSAGING_CWD as the working directory for terminal commands in messaging mode
This looks like a regression / incomplete fix relative to #2891: the direct os.getcwd() path was removed from gateway prompt building, but an indirect cli.py import can still re-poison TERMINAL_CWD under launchd.
Possible Fixes
Primary fix
Stop gateway / non-CLI codepaths from importing cli.py or depending on import-time CLI config mutation, so messaging cwd resolution stays independent from process cwd.
Defense in depth
Reconsider whether launchd should install with WorkingDirectory=$PROJECT_ROOT, or at least ensure that value can never leak into messaging context resolution.
Current Workaround
Removing terminal.cwd: "." from the user config avoids the bad behavior locally, but that should not be required for a default supported launchd install to behave correctly.
Bug Description
fix(gateway): stop loading hermes repo AGENTS.md into gateway sessions (~10k wasted tokens)(#2891) fixed the directos.getcwd()path inrun_agent.py, but the underlying bug can still happen on macOS launchd installs, which are a canonically supported gateway mode.In the current macOS service setup, the generated launchd plist sets the gateway process
WorkingDirectoryto the Hermes repo root. That would be fine if it stayed an internal service-manager detail, butcli.pycan still be imported indirectly during normal gateway runtime and has an import/config side effect that rewritesterminal.cwd: .toos.getcwd()for the local backend. Under launchd, that becomes the repo path, soTERMINAL_CWDcan get flipped to$HOME/.hermes/hermes-agent.Once that happens, the gateway again loads the repo's top-level
AGENTS.mdas project context, contrary to the documented messaging behavior (~by default, orMESSAGING_CWDwhen set).This is not a weird/nonstandard launchd setup. It is a supported path bug caused by the interaction between:
terminal.cwd: ".")Steps to Reproduce
terminal.backend: localterminal.cwd: .cli.pyvia:run_agent.py::_cap_delegate_task_calls()tools.delegate_tool::_get_max_concurrent_children()tools.delegate_tool::_load_config()from cli import CLI_CONFIGcli.pyloads config at import time, it resolvesterminal.cwd: .toos.getcwd()and writes that back toTERMINAL_CWD.TERMINAL_CWDbecomes$HOME/.hermes/hermes-agent.TERMINAL_CWDfor context discovery and load the repoAGENTS.mdagain.Notes:
MESSAGING_CWDis not required to reproduce the underlying bug.MESSAGING_CWDjust makes the expected-vs-actual mismatch more obvious.Expected Behavior
Messaging gateway sessions should use the documented messaging working directory semantics:
~MESSAGING_CWDwhen configuredThe launchd/systemd service process working directory should not leak into the agent's effective project-context cwd.
More precisely: the launchd service may run with
WorkingDirectory=$PROJECT_ROOT, but messaging sessions must not treat that as the user/project workspace.Actual Behavior
On macOS launchd installs,
TERMINAL_CWDcan be rewritten to the Hermes repo path during normal runtime, causing the gateway to load the repo'sAGENTS.mdinto system prompts again.Environment
v2026.4.13/1af2e18d408a9dcc2c61d6fc1eef5c6667f8e254ai.hermes.gateway)Error Output
No exception is required to trigger this; it is a runtime config/context leak.
Additional Context
Relevant code / evidence:
hermes_cli/gateway.pygenerates launchd service files withWorkingDirectory={PROJECT_ROOT}cli.pydefault config includesterminal.cwd = "."cli-config.yaml.examplealso includescwd: "."cli.py::load_cli_config()rewritesterminal.cwd: .toos.getcwd()for the local backend and exports it toTERMINAL_CWDrun_agent.py::_cap_delegate_task_calls()importstools.delegate_tool::_get_max_concurrent_children()on tool-call turnstools.delegate_tool::_load_config()first triesfrom cli import CLI_CONFIG, which triggers the import/config side effect aboverun_agent.pyusesTERMINAL_CWDfor context file discovery, so once it points at the repo, the repoAGENTS.mdis loaded againgateway/run.pystill documents / implements messaging fallback semantics asMESSAGING_CWDorPath.home()hermes gateway install/start/stop/statusDocs that seem inconsistent with the current behavior:
website/docs/user-guide/messaging/index.mddocuments launchd as a supported gateway modewebsite/docs/user-guide/configuration.mdsays messaging gateway cwd should be~by default orMESSAGING_CWDwebsite/docs/reference/environment-variables.mddocumentsMESSAGING_CWDas the working directory for terminal commands in messaging modeThis looks like a regression / incomplete fix relative to #2891: the direct
os.getcwd()path was removed from gateway prompt building, but an indirectcli.pyimport can still re-poisonTERMINAL_CWDunder launchd.Possible Fixes
Primary fix
Stop gateway / non-CLI codepaths from importing
cli.pyor depending on import-time CLI config mutation, so messaging cwd resolution stays independent from process cwd.Defense in depth
Reconsider whether launchd should install with
WorkingDirectory=$PROJECT_ROOT, or at least ensure that value can never leak into messaging context resolution.Current Workaround
Removing
terminal.cwd: "."from the user config avoids the bad behavior locally, but that should not be required for a default supported launchd install to behave correctly.