Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 33 additions & 7 deletions gateway/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,36 @@ def _home_target_env_var(platform_name: str) -> str:
load_hermes_dotenv(hermes_home=_hermes_home, project_env=Path(__file__).resolve().parents[1] / '.env')


def _reload_runtime_env_preserving_config_authority() -> None:
"""Reload .env for fresh credentials without letting stale .env override config.

Gateway processes are long-lived, so per-turn code reloads ~/.hermes/.env to
pick up rotated API keys. config.yaml remains authoritative for agent budget
settings such as agent.max_turns; otherwise a stale HERMES_MAX_ITERATIONS in
.env can replace the startup bridge on later turns.
"""
load_hermes_dotenv(
hermes_home=_hermes_home,
project_env=Path(__file__).resolve().parents[1] / '.env',
)

config_path = _hermes_home / 'config.yaml'
if not config_path.exists():
return
try:
import yaml as _yaml
with open(config_path, encoding="utf-8") as f:
cfg = _yaml.safe_load(f) or {}
from hermes_cli.config import _expand_env_vars
cfg = _expand_env_vars(cfg)
except Exception:
return

agent_cfg = cfg.get("agent", {})
if isinstance(agent_cfg, dict) and "max_turns" in agent_cfg:
os.environ["HERMES_MAX_ITERATIONS"] = str(agent_cfg["max_turns"])


_DOCKER_VOLUME_SPEC_RE = re.compile(r"^(?P<host>.+):(?P<container>/[^:]+?)(?::(?P<options>[^:]+))?$")
_DOCKER_MEDIA_OUTPUT_CONTAINER_PATHS = {"/output", "/outputs"}

Expand Down Expand Up @@ -12235,13 +12265,9 @@ def run_sync():
combined_ephemeral = (combined_ephemeral + "\n\n" + self._ephemeral_system_prompt).strip()

# Re-read .env and config for fresh credentials (gateway is long-lived,
# keys may change without restart).
try:
load_dotenv(_env_path, override=True, encoding="utf-8")
except UnicodeDecodeError:
load_dotenv(_env_path, override=True, encoding="latin-1")
except Exception:
pass
# keys may change without restart). Keep config.yaml authoritative for
# runtime budget settings bridged into env vars.
_reload_runtime_env_preserving_config_authority()

try:
model, runtime_kwargs = self._resolve_session_agent_runtime(
Expand Down
53 changes: 53 additions & 0 deletions tests/gateway/test_runtime_env_reload_config_authority.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"""Regression tests for gateway per-turn env reload preserving config authority.

Issue #19158: startup bridges config.yaml agent.max_turns into
HERMES_MAX_ITERATIONS, but a later per-turn load_dotenv(..., override=True)
can restore a stale .env HERMES_MAX_ITERATIONS value before the next turn.
"""

from __future__ import annotations

import os
from pathlib import Path

import yaml

from gateway import run as gateway_run


def test_reload_runtime_env_preserves_config_max_turns(tmp_path: Path, monkeypatch) -> None:
hermes_home = tmp_path / ".hermes"
hermes_home.mkdir()
(hermes_home / "config.yaml").write_text(
yaml.safe_dump({"agent": {"max_turns": 9000}}),
encoding="utf-8",
)
(hermes_home / ".env").write_text(
"HERMES_MAX_ITERATIONS=90\nOPENROUTER_API_KEY=fresh-key\n",
encoding="utf-8",
)

monkeypatch.setattr(gateway_run, "_hermes_home", hermes_home)
monkeypatch.setenv("HERMES_MAX_ITERATIONS", "9000")
monkeypatch.delenv("OPENROUTER_API_KEY", raising=False)

gateway_run._reload_runtime_env_preserving_config_authority()

assert os.environ["OPENROUTER_API_KEY"] == "fresh-key"
assert os.environ["HERMES_MAX_ITERATIONS"] == "9000"


def test_reload_runtime_env_keeps_env_max_iterations_when_config_omits_key(
tmp_path: Path, monkeypatch
) -> None:
hermes_home = tmp_path / ".hermes"
hermes_home.mkdir()
(hermes_home / "config.yaml").write_text(yaml.safe_dump({"agent": {}}), encoding="utf-8")
(hermes_home / ".env").write_text("HERMES_MAX_ITERATIONS=123\n", encoding="utf-8")

monkeypatch.setattr(gateway_run, "_hermes_home", hermes_home)
monkeypatch.delenv("HERMES_MAX_ITERATIONS", raising=False)

gateway_run._reload_runtime_env_preserving_config_authority()

assert os.environ["HERMES_MAX_ITERATIONS"] == "123"