Skip to content

fix(state): degrade list_sessions_rich on stale read-only profile DBs - #80126

Closed
JuizSpeaking wants to merge 1 commit into
NousResearch:mainfrom
JuizSpeaking:fix/stale-readonly-profile-sessions
Closed

fix(state): degrade list_sessions_rich on stale read-only profile DBs#80126
JuizSpeaking wants to merge 1 commit into
NousResearch:mainfrom
JuizSpeaking:fix/stale-readonly-profile-sessions

Conversation

@JuizSpeaking

Copy link
Copy Markdown

Summary

The desktop sidebar (GET /api/profiles/sessions/sidebar) aggregates every profile's state.db read-only, and read-only opens deliberately skip schema reconciliation. A dormant profile's DB can therefore be older than the running binary, missing columns the list queries select unconditionally (last_activity_at, last_read_at, pinned, …). The sidebar's per-profile try/except then swallows the resulting no such column error and silently drops the whole profile from the session list — which reads as "all sessions are gone except the active profile's."

Fix

list_sessions_rich now probes the live sessions columns once per SessionDB instance and degrades instead of raising on stale-schema read-only opens:

  • Activity expression (_sql_session_last_active / _by_id): falls back to MAX(messages.timestamp) + started_at when last_activity_at is absent. The UNION ALL keeps a NULL arm so the MAX subquery stays well-formed.
  • Compact projection (_compact_session_cols_live): intersected with the live columns, so compact_rows=True never selects a column the DB doesn't have.
  • Pinned back-fill: skipped when the pinned column is absent.
  • Compression-tip batch fetch (_get_session_rich_rows_batch): follows the same guard since it backs list_sessions_rich's projection.

Writable opens are unaffected: they still reconcile the schema at startup, so the probe always sees the full column set there.

Tests

New tests/test_hermes_state_readonly_stale_schema.py:

  • compact_rows listing against a genuinely stale read-only DB returns rows (degraded last_active) instead of raising
  • fresh-schema DBs still satisfy the declared projection contract
  • _compact_session_cols_live intersection behavior

Focused suite: 216 passed (state, readonly-preflight, WAL-fallback + new file).

Repro

db = SessionDB(db_path=stale_db, read_only=True)
db.list_sessions_rich(compact_rows=True, order_by_last_active=True, include_pinned=True)
# before: OperationalError: no such column: last_activity_at
# after:  rows with last_active derived from messages/started_at

The desktop sidebar aggregates every profile's state.db read-only, and
read-only opens deliberately skip schema reconciliation. A dormant
profile's DB can therefore be older than the running binary, missing
columns the list queries select unconditionally (last_activity_at,
last_read_at, pinned, ...). The sidebar's per-profile try/except then
swallows the 'no such column' error and silently drops the whole
profile from the session list — reads as 'sessions are gone'.

list_sessions_rich now probes the live sessions columns once per
SessionDB instance and degrades instead of raising:
- activity expression falls back to messages-max + started_at when
  last_activity_at is absent (UNION ALL keeps a NULL arm so the
  MAX subquery stays well-formed)
- compact projection is intersected with the live columns
- pinned back-fill is skipped when the pinned column is absent

The compression-tip batch fetch (_get_session_rich_rows_batch) follows
the same guard since it backs list_sessions_rich's projection.

Adds regression tests covering a genuinely stale read-only DB.
@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades labels Aug 6, 2026
@jquesnelle

Copy link
Copy Markdown
Collaborator

Appreciate the contribution! Closed by #80797

@jquesnelle jquesnelle closed this Aug 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P3 Low — cosmetic, nice to have sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants