fix(state): one corrupt timestamp row no longer kills sessions list, export or insights
SQLite dynamic typing lets a TEXT cell ('not-a-timestamp'), inf/nan or a
garbage double (8.4e252 salvaged from a damaged page) sit in a REAL
timestamp column. Every reader called datetime.fromtimestamp()/float
arithmetic on the raw cell, so ONE bad row raised TypeError/OverflowError
out of the row loop and took down the whole `hermes sessions list`/browse
table (#102399), all three exporters — JSONL/MD, QMD, HTML (#102352) —
and `hermes insights` (#99959).
Fix the class with ONE helper, hermes_cli.timefmt.coerce_epoch(): a
stored cell becomes float epoch seconds inside a sane 1970..2103 window
or None after a WARNING that names the session id. Every reader routes
through it — relative_time (list/browse/resume picker), format_epoch
(prune/candidates tables), the three exporters' timestamp formatters,
insights' _get_sessions/_day/period range — so a bad row renders as
'?'/'N/A'/raw text for that one cell and the command completes.
Write side: hermes_state_messages._coerce_timestamp (append_message,
append_messages_batch, import) and the import path's started_at now use
the same window, so a new out-of-range timestamp falls back to now()
instead of being persisted — new bad rows cannot be written by Hermes.
Reported-by: #102399, #102352, #99959 reporters; kokhlo's insights
analysis pointed at every reporting site, not just line 860.
This commit is contained in:
@@ -11,6 +11,7 @@ from typing import Any, Dict, List, Optional
|
||||
|
||||
from agent.skill_commands import SKILL_SCAFFOLD_SQL_LIKE
|
||||
from utils import safe_json_loads
|
||||
from hermes_cli.timefmt import coerce_epoch
|
||||
from hermes_state_common import SCHEMA_SQL, _PREVIEW_RAW_SUBQUERY_SQL, _shape_preview, _sql_session_last_active
|
||||
|
||||
# Pre-split logger identity so log filtering/capture is unchanged.
|
||||
@@ -465,7 +466,7 @@ class SessionPortabilityMixin:
|
||||
|
||||
def _import_session_row(self, conn, raw: Dict[str, Any], messages: List[Dict[str, Any]], session_id: str) -> None:
|
||||
"""INSERT one normalized session + its messages; counts fixed up after."""
|
||||
started_at = self._coerce_or(raw.get("started_at"), float, None)
|
||||
started_at = coerce_epoch(raw.get("started_at"), session_id=session_id, field="started_at")
|
||||
params = {
|
||||
"id": session_id, "source": str(raw.get("source") or "import"),
|
||||
"system_prompt_hash": self._store_system_prompt(conn, raw.get("system_prompt")),
|
||||
|
||||
Reference in New Issue
Block a user