feat(sessions): drop empty sessions on CLI exit and session rotation
Port from google-gemini/gemini-cli#27770: starting the CLI and immediately quitting (or rotating with /new, /clear) left an empty untitled session row behind. These ghost rows pile up in /resume, `hermes sessions list`, and the in-chat recent-sessions browser. - SessionDB.delete_session_if_empty(): transactional check-and-delete that only removes rows with no messages, no title, and no child sessions (delegate subagent parents are preserved). Also removes on-disk transcript files via the existing _remove_session_files. - HermesCLI._discard_session_if_empty(): thin wrapper, wired into the cli_close shutdown path and the new_session() rotation path. Skipped when /exit --delete already handles removal. Unlike the one-shot prune_empty_ghost_sessions migration (TUI-only, 24h-old rows), this prevents new ghost rows from accumulating at the moment they would be created.
This commit is contained in:
@@ -5821,6 +5821,29 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin):
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _discard_session_if_empty(self, session_id: Optional[str]) -> bool:
|
||||
"""Drop a just-ended session row when it never gained content.
|
||||
|
||||
Starting the CLI and immediately quitting (or rotating with /new,
|
||||
/clear) used to leave an empty untitled row behind that clutters
|
||||
``/resume`` and ``hermes sessions list``. Delegates the
|
||||
check-and-delete to ``SessionDB.delete_session_if_empty``, which
|
||||
only removes rows with no messages, no title, and no child
|
||||
sessions. Ported from google-gemini/gemini-cli#27770.
|
||||
"""
|
||||
if not self._session_db or not session_id:
|
||||
return False
|
||||
try:
|
||||
from hermes_constants import get_hermes_home as _ghh
|
||||
return self._session_db.delete_session_if_empty(
|
||||
session_id, sessions_dir=_ghh() / "sessions"
|
||||
)
|
||||
except Exception:
|
||||
logger.debug(
|
||||
"Could not prune empty session %s", session_id, exc_info=True
|
||||
)
|
||||
return False
|
||||
|
||||
def new_session(self, silent=False, title=None):
|
||||
"""Start a fresh session with a new session ID and cleared agent state."""
|
||||
if self.agent and self.conversation_history:
|
||||
@@ -5837,6 +5860,9 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin):
|
||||
self._session_db.end_session(old_session_id, "new_session")
|
||||
except Exception:
|
||||
pass
|
||||
# Don't let immediately-rotated empty sessions pile up in
|
||||
# /resume and `hermes sessions list` (gemini-cli#27770 port).
|
||||
self._discard_session_if_empty(old_session_id)
|
||||
|
||||
self.session_start = datetime.now()
|
||||
timestamp_str = self.session_start.strftime("%Y%m%d_%H%M%S")
|
||||
@@ -13074,6 +13100,15 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin):
|
||||
self._session_db.end_session(self.agent.session_id, "cli_close")
|
||||
except (Exception, KeyboardInterrupt) as e:
|
||||
logger.debug("Could not close session in DB: %s", e)
|
||||
# Started-and-immediately-quit sessions never gained content;
|
||||
# drop the empty row so /resume and `hermes sessions list`
|
||||
# stay clean (gemini-cli#27770 port). No-op for resumed or
|
||||
# titled sessions and anything with messages or children.
|
||||
if not getattr(self, '_delete_session_on_exit', False):
|
||||
try:
|
||||
self._discard_session_if_empty(self.agent.session_id)
|
||||
except (Exception, KeyboardInterrupt) as e:
|
||||
logger.debug("Could not prune empty session: %s", e)
|
||||
# /exit --delete: also remove the current session's transcripts
|
||||
# and SQLite history. Ported from google-gemini/gemini-cli#19332.
|
||||
if getattr(self, '_delete_session_on_exit', False):
|
||||
|
||||
Reference in New Issue
Block a user