diff --git a/contributors/emails/jorkeyliu@gmail.com b/contributors/emails/jorkeyliu@gmail.com new file mode 100644 index 00000000000..6973bf9bb5f --- /dev/null +++ b/contributors/emails/jorkeyliu@gmail.com @@ -0,0 +1,2 @@ +JorkeyLiu +# PR #11875 salvage diff --git a/hermes_state.py b/hermes_state.py index 5a06c175760..c23487ca30e 100644 --- a/hermes_state.py +++ b/hermes_state.py @@ -234,6 +234,30 @@ T = TypeVar("T") DEFAULT_DB_PATH = get_hermes_home() / "state.db" +# Import-time snapshot used by _default_db_path() to detect a deliberately +# re-pointed DEFAULT_DB_PATH (tests monkeypatch the constant directly). +_IMPORT_DEFAULT_DB_PATH = DEFAULT_DB_PATH + + +def _default_db_path() -> Path: + """Resolve the default state DB path at call time. + + ``DEFAULT_DB_PATH`` is computed when this module is first imported, which + freezes the developer's real ``~/.hermes`` even when a test fixture later + redirects ``HERMES_HOME`` — importing this module during collection was + enough to point every default ``SessionDB()`` at the real state.db. + + Precedence: + + 1. A deliberately re-pointed ``DEFAULT_DB_PATH`` (differs from the + import-time snapshot — the established test escape hatch) wins. + 2. Otherwise resolve ``get_hermes_home()`` fresh so a runtime + ``HERMES_HOME`` redirect takes effect regardless of import order. + """ + if DEFAULT_DB_PATH != _IMPORT_DEFAULT_DB_PATH: + return DEFAULT_DB_PATH + return get_hermes_home() / "state.db" + # --------------------------------------------------------------------------- # WAL-compatibility fallback # --------------------------------------------------------------------------- @@ -1765,7 +1789,7 @@ class SessionDB(SessionSearchMixin, SessionSchemaMixin, SessionPortabilityMixin) _IMPORT_MAX_TOTAL_BYTES = 25 * 1024 * 1024 def __init__(self, db_path: Path = None, read_only: bool = False): - self.db_path = db_path or DEFAULT_DB_PATH + self.db_path = db_path or _default_db_path() self.read_only = read_only self._lock = threading.Lock() diff --git a/tests/gateway/test_session_store_prune.py b/tests/gateway/test_session_store_prune.py index f3fa9bcf4b8..d171f527b9e 100644 --- a/tests/gateway/test_session_store_prune.py +++ b/tests/gateway/test_session_store_prune.py @@ -24,6 +24,29 @@ from gateway.config import GatewayConfig, Platform, SessionResetPolicy from gateway.session import SessionEntry, SessionStore +def test_session_store_default_db_uses_runtime_hermes_home(tmp_path, monkeypatch): + """SessionStore must honor runtime HERMES_HOME when opening the default DB. + + Regression for the import-time DEFAULT_DB_PATH freeze: importing + hermes_state before a fixture redirected HERMES_HOME used to pin every + default SessionDB() at the developer's real ~/.hermes/state.db. + """ + config = GatewayConfig(default_reset_policy=SessionResetPolicy(mode="none")) + fake_home = tmp_path / "alt_hermes_home" + fake_home.mkdir() + monkeypatch.setenv("HERMES_HOME", str(fake_home)) + + with patch("gateway.session.SessionStore._ensure_loaded"): + store = SessionStore(sessions_dir=tmp_path / "sessions", config=config) + + try: + assert store._db is not None + assert store._db.db_path == fake_home / "state.db" + finally: + if store._db is not None: + store._db.close() + + def _make_store(tmp_path, max_age_days: int = 90, has_active_processes_fn=None): """Build a SessionStore bypassing SQLite/disk-load side effects.""" config = GatewayConfig( diff --git a/tools/session_search_tool.py b/tools/session_search_tool.py index 99a9684c289..ed9fd49502c 100644 --- a/tools/session_search_tool.py +++ b/tools/session_search_tool.py @@ -954,8 +954,8 @@ def session_search( def check_session_search_requirements() -> bool: """Requires the SQLite state database.""" try: - from hermes_state import DEFAULT_DB_PATH - return DEFAULT_DB_PATH.parent.exists() + from hermes_state import _default_db_path + return _default_db_path().parent.exists() except ImportError: return False