fix(recovery): name the real state.db in the copy-pasteable recovery banners

The gateway broadcast and turn-failure explanation printed a literal
~/.hermes/state.db; now that the line is a command the operator is meant to
run as-is, interpolate _default_db_path() so profile / HERMES_HOME installs are
pointed at the store that actually failed. Also: split a comment that a merge
fused onto the logger line in session_lost_and_found.py, and fix an inverted
test docstring.
This commit is contained in:
kshitijk4poor
2026-09-03 04:45:41 +05:30
committed by kshitij
parent a15f96450b
commit 914d8a0bd6
4 changed files with 23 additions and 9 deletions
+10 -3
View File
@@ -27476,20 +27476,27 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
if not error:
return
from hermes_state import classify_persistence_error, format_session_db_unavailable
from hermes_state import (
_default_db_path,
classify_persistence_error,
format_session_db_unavailable,
)
cause = classify_persistence_error(error)
hint = format_session_db_unavailable()
if cause == "corrupt":
# Copy-pasteable, so name the real store (profiles / HERMES_HOME
# do not live under ~/.hermes).
db_path = _default_db_path()
message = (
"⚠️ Session database corruption detected. Messages may not be "
"persisted. Recovery options:\n"
"1. Run `hermes doctor --fix`\n"
"2. Stop the gateway, then recover with:\n"
" hermes sessions recover --source ~/.hermes/state.db "
f" hermes sessions recover --source {db_path} "
"--inspect-only\n"
" (if it reports recoverable) hermes sessions recover "
"--source ~/.hermes/state.db --output recovered-state.db\n"
f"--source {db_path} --output recovered-state.db\n"
" — recovery snapshots the damaged file first; do NOT run "
"`sqlite3 ... \".recover\"` against the live state.db, a "
"vulnerable sqlite3 CLI can corrupt it further\n"
+3 -1
View File
@@ -32,7 +32,9 @@ import tempfile
from pathlib import Path
from typing import Any, Optional
logger = logging.getLogger(__name__)# Hermes session ids are timestamps: 20260812_135332_ab12cd. This is the
logger = logging.getLogger(__name__)
# Hermes session ids are timestamps: 20260812_135332_ab12cd. This is the
# strongest sentinel available for classifying schema-less rows.
SESSION_ID_PATTERN = re.compile(r"^\d{8}_\d{6}_")
+7 -2
View File
@@ -4423,6 +4423,11 @@ class AIAgent:
"pending_messages/pending-*.json."
)
if cause == "corrupt":
from hermes_state import _default_db_path
# Copy-pasteable, so name the real store (profiles /
# HERMES_HOME do not live under ~/.hermes).
db_path = _default_db_path()
return (
prefix
+ "the turn was stopped because the state database "
@@ -4431,10 +4436,10 @@ class AIAgent:
"not help. Recovery options:\n"
"1. Run `hermes doctor --fix`\n"
"2. Stop the gateway, then recover with:\n"
" hermes sessions recover --source ~/.hermes/state.db "
f" hermes sessions recover --source {db_path} "
"--inspect-only\n"
" (if it reports recoverable) hermes sessions recover "
"--source ~/.hermes/state.db --output recovered-state.db\n"
f"--source {db_path} --output recovered-state.db\n"
" — recovery snapshots the damaged file first; do NOT "
"run `sqlite3 ... \".recover\"` against the live "
"state.db, a vulnerable sqlite3 CLI can corrupt it "
@@ -307,9 +307,9 @@ class TestEmittedCommandsSatisfyCliContract:
return argparse.Namespace(**fields)
def test_old_v1_shape_is_still_rejected(self, tmp_path):
"""Guard the test's own premise: neither --inspect-only nor
--output (the shape the v1 banner printed) is rejected with rc 2
by the real dispatcher."""
"""Guard the test's own premise: the bare `--source <db>` shape the
v1 banner printed (neither --inspect-only nor --output) is rejected
with rc 2 by the real dispatcher."""
import hermes_cli.sessions_cmd as sc
rc = sc.cmd_sessions(self._namespace(tmp_path / "state.db"))