From c312dd36ca5c3fe19e544143b19b12207d442e8d Mon Sep 17 00:00:00 2001 From: nesquena-hermes Date: Sat, 4 Apr 2026 20:08:20 -0700 Subject: [PATCH] fix: state_sync.py -- correct class name, constructor type, title API, connection leak Three bugs found during review: 1. Class is SessionDB not HermesState -- would silently no-op on every install 2. SessionDB.__init__ takes Path not str -- would crash with AttributeError 3. _execute_write() takes a callable not SQL+params -- wrong signature. Replaced with public set_session_title() API. 4. Each call opened a persistent SQLite connection and never closed it. Added try/finally db.close() to prevent WAL leak under sustained load. Co-authored-by: Nathan Esquenazi --- api/state_sync.py | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/api/state_sync.py b/api/state_sync.py index db5868a..d3f5d9e 100644 --- a/api/state_sync.py +++ b/api/state_sync.py @@ -18,11 +18,12 @@ from pathlib import Path def _get_state_db(): - """Get a HermesState instance for the active profile's state.db. + """Get a SessionDB instance for the active profile's state.db. Returns None if hermes_state is not importable or DB is unavailable. + Each caller is responsible for calling db.close() when done. """ try: - from hermes_state import HermesState + from hermes_state import SessionDB except ImportError: return None @@ -37,7 +38,7 @@ def _get_state_db(): return None try: - return HermesState(str(db_path)) + return SessionDB(db_path) except Exception: return None @@ -46,10 +47,10 @@ def sync_session_start(session_id, model=None): """Register a WebUI session in state.db (idempotent). Called when a session's first message is sent. """ + db = _get_state_db() + if not db: + return try: - db = _get_state_db() - if not db: - return db.ensure_session( session_id=session_id, source='webui', @@ -57,6 +58,11 @@ def sync_session_start(session_id, model=None): ) except Exception: pass # never crash the WebUI for sync failures + finally: + try: + db.close() + except Exception: + pass def sync_session_usage(session_id, input_tokens=0, output_tokens=0, @@ -65,10 +71,10 @@ def sync_session_usage(session_id, input_tokens=0, output_tokens=0, Called after each turn completes. Uses absolute=True to set totals (the WebUI Session already accumulates across turns). """ + db = _get_state_db() + if not db: + return try: - db = _get_state_db() - if not db: - return # Ensure session exists first (idempotent) db.ensure_session(session_id=session_id, source='webui', model=model) # Set absolute token counts @@ -80,14 +86,16 @@ def sync_session_usage(session_id, input_tokens=0, output_tokens=0, model=model, absolute=True, ) - # Update title if we have one + # Update title if we have one, using the public API if title: try: - db._execute_write( - "UPDATE sessions SET title = ? WHERE id = ?", - (title, session_id), - ) + db.set_session_title(session_id, title) except Exception: pass except Exception: pass # never crash the WebUI for sync failures + finally: + try: + db.close() + except Exception: + pass