refactor(agent): key the session-row heal on the classified cause

The heal predicate (errno 787 or a 'foreign key constraint' substring)
and the classifier phrase were two separate definitions of the same
failure. Move the SQLITE_CONSTRAINT_FOREIGNKEY code check into
classify_persistence_error, document the bucket, and branch the heal on
agent._last_persistence_error_cause == 'session_row_missing' so the two
can't drift.
This commit is contained in:
kshitijk4poor
2026-09-26 18:25:08 +05:30
committed by kshitij
parent e9a9859834
commit d3faaee370
2 changed files with 6 additions and 4 deletions

View File

@@ -5,7 +5,6 @@ import hashlib
import logging
import re
import sqlite3
from contextlib import nullcontext
from typing import Any, Dict, List, Optional, Tuple
@@ -306,8 +305,7 @@ def _db_flush_failed(agent, e: Exception, batch_rows: List[Dict[str, Any]], adop
from hermes_state import StateDbCorruptError, StateDbReplacedError, classify_persistence_error, divert_session_transcript_jsonl
from hermes_state_errors import CompressionSessionClosedError
agent._last_persistence_error_cause = classify_persistence_error(e)
if getattr(e, "sqlite_errorcode", None) == getattr(sqlite3, "SQLITE_CONSTRAINT_FOREIGNKEY", 787) \
or "foreign key constraint" in str(e).lower():
if agent._last_persistence_error_cause == "session_row_missing":
# The session row was removed under this live agent (`hermes sessions delete`, the Desktop/web
# delete, bulk prune, a profile-repair move, an in-place store rebuild — none visible to the
# cached agent, so the cached `_session_db_created` flag is stale and every later append hits

View File

@@ -38,6 +38,7 @@ _TRANSIENT_SQLITE_MARKERS = (
# on its %_config read but replaces the text with "vtable constructor failed: messages_fts",
# so a phrase match read a busy store as a hard failure.
_SQLITE_LOCK_CODES = (sqlite3.SQLITE_BUSY, sqlite3.SQLITE_LOCKED)
_SQLITE_CONSTRAINT_FOREIGNKEY = getattr(sqlite3, "SQLITE_CONSTRAINT_FOREIGNKEY", 787) # extended code
def _sqlite_primary_code(exc_or_str) -> "int | None":
@@ -273,7 +274,8 @@ def classify_persistence_error(exc_or_str) -> str:
file damage (repair path, not disk space); "fts_index" = SQLite scoped the
corruption to the FTS index (the transcript store is not damaged); "replaced" =
main-file replacement; "deleted_wal" = a retired sidecar generation requiring
capture inspection."""
capture inspection; "session_row_missing" = the session row was deleted under a
live agent (FK rejection; the flush recreates it)."""
if exc_or_str is None:
return "unknown"
# Lease refusals contain neither "locked" nor "busy": match by type first,
@@ -289,6 +291,8 @@ def classify_persistence_error(exc_or_str) -> str:
return "fts_index"
if _sqlite_primary_code(exc_or_str) in _SQLITE_LOCK_CODES:
return "locked"
if getattr(exc_or_str, "sqlite_errorcode", None) == _SQLITE_CONSTRAINT_FOREIGNKEY:
return "session_row_missing"
text = str(exc_or_str).lower()
for markers, cause in _PERSISTENCE_CAUSE_BY_PHRASE:
if any(marker in text for marker in markers):