mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-08 13:12:08 +00:00
* fix(gateway): use process-level HERMES_HOME for identity files Gateway identity files (PID, lock, runtime status, takeover/stop markers) were written via get_hermes_home() which honours the _HERMES_HOME_OVERRIDE contextvar used for per-session profile dispatch. When a profile-context task happened to be active at write time, files landed in the wrong profile directory. Add _get_process_hermes_home() that skips the contextvar and uses only the HERMES_HOME env var or platform default, and route all gateway identity file paths through it. Fixes #56986 * chore(release): map liuhao1024 author email for PR #56993 salvage --------- Co-authored-by: liuhao1024 <sunsky.lau@gmail.com> Co-authored-by: Ben <ben@nousresearch.com>
This commit is contained in:
parent
4b9d9b205b
commit
043e71f1f4
3 changed files with 59 additions and 7 deletions
|
|
@ -20,7 +20,7 @@ import subprocess
|
|||
import sys
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from hermes_constants import get_hermes_home
|
||||
from hermes_constants import get_hermes_home, _get_platform_default_hermes_home
|
||||
from typing import Any, Optional
|
||||
from utils import atomic_json_write
|
||||
|
||||
|
|
@ -42,9 +42,25 @@ _gateway_lock_handle = None
|
|||
_WINDOWS_LOCK_OFFSET = 1024 * 1024
|
||||
|
||||
|
||||
def _get_process_hermes_home() -> Path:
|
||||
"""Return the process-level HERMES_HOME, skipping context-local overrides.
|
||||
|
||||
Gateway identity files (PID, lock, runtime status, takeover/stop markers)
|
||||
must always live in the directory the gateway process was launched with.
|
||||
``get_hermes_home()`` honors ``_HERMES_HOME_OVERRIDE`` contextvar used for
|
||||
per-session profile dispatch, which would route these files into the wrong
|
||||
profile directory when a profile-context task happens to be active at write
|
||||
time. See issue #56986.
|
||||
"""
|
||||
val = os.environ.get("HERMES_HOME", "").strip()
|
||||
if val:
|
||||
return Path(val)
|
||||
return _get_platform_default_hermes_home()
|
||||
|
||||
|
||||
def _get_pid_path() -> Path:
|
||||
"""Return the path to the gateway PID file, respecting HERMES_HOME."""
|
||||
home = get_hermes_home()
|
||||
home = _get_process_hermes_home()
|
||||
return home / "gateway.pid"
|
||||
|
||||
|
||||
|
|
@ -52,7 +68,7 @@ def _get_gateway_lock_path(pid_path: Optional[Path] = None) -> Path:
|
|||
"""Return the path to the runtime gateway lock file."""
|
||||
if pid_path is not None:
|
||||
return pid_path.with_name(_GATEWAY_LOCK_FILENAME)
|
||||
home = get_hermes_home()
|
||||
home = _get_process_hermes_home()
|
||||
return home / _GATEWAY_LOCK_FILENAME
|
||||
|
||||
|
||||
|
|
@ -1136,13 +1152,13 @@ _PLANNED_STOP_MARKER_TTL_S = 60
|
|||
|
||||
def _get_takeover_marker_path() -> Path:
|
||||
"""Return the path to the --replace takeover marker file."""
|
||||
home = get_hermes_home()
|
||||
home = _get_process_hermes_home()
|
||||
return home / _TAKEOVER_MARKER_FILENAME
|
||||
|
||||
|
||||
def _get_planned_stop_marker_path() -> Path:
|
||||
"""Return the path to the intentional gateway stop marker file."""
|
||||
home = get_hermes_home()
|
||||
home = _get_process_hermes_home()
|
||||
return home / _PLANNED_STOP_MARKER_FILENAME
|
||||
|
||||
|
||||
|
|
@ -1197,7 +1213,7 @@ def _consume_pid_marker_for_self(
|
|||
# unaffected. Leave a mismatched marker in place so the correct
|
||||
# profile can still consume it.
|
||||
replacer_home = record.get("replacer_hermes_home")
|
||||
if replacer_home is not None and replacer_home != str(get_hermes_home()):
|
||||
if replacer_home is not None and replacer_home != str(_get_process_hermes_home()):
|
||||
return False
|
||||
|
||||
our_pid = os.getpid()
|
||||
|
|
@ -1246,7 +1262,7 @@ def write_takeover_marker(target_pid: int) -> bool:
|
|||
"target_pid": target_pid,
|
||||
"target_start_time": target_start_time,
|
||||
"replacer_pid": os.getpid(),
|
||||
"replacer_hermes_home": str(get_hermes_home()),
|
||||
"replacer_hermes_home": str(_get_process_hermes_home()),
|
||||
"written_at": _utc_now_iso(),
|
||||
}
|
||||
_write_json_file(_get_takeover_marker_path(), record)
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ AUTHOR_MAP = {
|
|||
"allenliang2022@users.noreply.github.com": "allenliang2022", # PR #56932 test coverage folded into #56909 salvage (408 → retryable timeout)
|
||||
"m888.braun@hotmail.com": "ManniBr", # PR #57417 partial salvage (gateway: fail-closed adapter resolution for unregistered secondary profiles)
|
||||
"austin@openvm067.space": "austinlaw076", # PR #57563 partial salvage (auth: lazy per-profile Anthropic OAuth file; gateway: whatsapp_cloud/line added to port-binding platform set)
|
||||
"sunsky.lau@gmail.com": "liuhao1024", # PR #56993 salvage (gateway: process-level HERMES_HOME for pid/lock/status identity files; #56986)
|
||||
"blueirobin02@gmail.com": "irresi", # PR #59048 salvage (gateway: scope reset banners' session info to the serving profile; #59003)
|
||||
"jashlee+microsoft@microsoft.com": "s905060", # PR #57943 salvage (photon: auto-reinstall stale sidecar node_modules when lockfile is newer than npm's install marker; #59169)
|
||||
"lohinth25@proton.me": "l0h1nth", # PR #32210 salvage (mattermost: accept leading-space slash commands from mobile clients; #25184)
|
||||
|
|
|
|||
|
|
@ -293,6 +293,41 @@ class TestGatewayPidState:
|
|||
finally:
|
||||
status.release_gateway_runtime_lock()
|
||||
|
||||
def test_gateway_identity_files_use_process_home_not_context_override(
|
||||
self, tmp_path, monkeypatch
|
||||
):
|
||||
"""Regression: pid/lock/state files must use process-level HERMES_HOME.
|
||||
|
||||
When a profile context override is active (e.g., during session dispatch
|
||||
for a named profile), gateway identity files should still be written to
|
||||
the process-level HERMES_HOME, not the profile's directory. See #56986.
|
||||
"""
|
||||
from hermes_constants import set_hermes_home_override, reset_hermes_home_override
|
||||
|
||||
process_home = tmp_path / "default"
|
||||
process_home.mkdir()
|
||||
profile_home = tmp_path / "profiles" / "cfo"
|
||||
profile_home.mkdir(parents=True)
|
||||
monkeypatch.setenv("HERMES_HOME", str(process_home))
|
||||
|
||||
# Simulate a profile context override being active during write.
|
||||
token = set_hermes_home_override(str(profile_home))
|
||||
try:
|
||||
status.write_pid_file()
|
||||
finally:
|
||||
reset_hermes_home_override(token)
|
||||
|
||||
# PID file must land in the process-level home, not the profile home.
|
||||
assert (process_home / "gateway.pid").exists()
|
||||
assert not (profile_home / "gateway.pid").exists()
|
||||
|
||||
payload = json.loads((process_home / "gateway.pid").read_text())
|
||||
assert payload["pid"] == os.getpid()
|
||||
|
||||
# Cleanup for atexit hooks.
|
||||
monkeypatch.setenv("HERMES_HOME", str(process_home))
|
||||
(process_home / "gateway.pid").unlink(missing_ok=True)
|
||||
|
||||
|
||||
class TestGatewayRuntimeStatus:
|
||||
def test_write_json_file_uses_atomic_json_write(self, tmp_path, monkeypatch):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue