Skip to content

fix(sessions): heal stale profile schemas on read - #80030

Closed
Tilly-YL wants to merge 1 commit into
NousResearch:mainfrom
Tilly-YL:fix/heal-profile-session-schema
Closed

fix(sessions): heal stale profile schemas on read#80030
Tilly-YL wants to merge 1 commit into
NousResearch:mainfrom
Tilly-YL:fix/heal-profile-session-schema

Conversation

@Tilly-YL

@Tilly-YL Tilly-YL commented Aug 6, 2026

Copy link
Copy Markdown

What does this PR do?

Hermes Desktop reads every profile's state.db in read-only mode when it builds the session list. Read-only opens skip schema reconciliation. The existing probe checked four named columns, so an older profile without sessions.last_read_at passed the probe and failed later with no such column: s.last_read_at. The cross-profile list and batched sidebar routes also opened SessionDB directly. They caught that failure and returned an empty list, which made intact sessions appear missing after an update or restart.

This change derives cached LIMIT 0 probes from every table and column declared in SCHEMA_SQL. A current database remains read-only. A stale profile follows the existing one-time writable reconciliation path and is reopened read-only. Both cross-profile session routes now use that shared helper.

Related Issue

Fixes #79531

This follows the direction introduced in #42487. The commit retains liuhao1024's credit and extends the fix to the current shared helper and the Desktop sidebar route. The reproduction and schema analysis from wangyi0177-eng in #79531 helped isolate the last_read_at case.

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Security fix
  • Documentation update
  • Tests (adding or improving test coverage)
  • Refactor (no behavior change)
  • New skill (bundled or hub)

Changes Made

hermes_cli/web_server.py now builds the read-only probes from SCHEMA_SQL and uses the existing writable reconciliation path when any declared table or column is missing.

hermes_cli/web_routers/profiles.py now routes both /api/profiles/sessions and /api/profiles/sessions/sidebar through _open_session_db_for_profile.

tests/hermes_cli/test_web_server.py covers missing last_read_at and profile_name columns, plus both cross-profile session endpoints.

How to Test

  1. Create a named profile database with the current SessionDB, add one session and one message, then remove sessions.last_read_at to model a database created before that column was added.
  2. Request /api/sessions, /api/profiles/sessions, and /api/profiles/sessions/sidebar. Each route should return the existing session, and PRAGMA table_info(sessions) should show that last_read_at was restored.
  3. Run the focused test and compatibility commands listed below.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass
  • I've added tests for my changes
  • I've tested on macOS 26.5.2

Documentation & Housekeeping

  • Relevant documentation is unchanged; docstrings were updated where behavior changed
  • No config keys were added or changed
  • No contributor workflow or architecture document changed
  • I considered Windows and macOS compatibility
  • No tool descriptions or schemas changed

Screenshots / Logs

python -m pytest tests/hermes_cli/test_web_server.py -q
143 passed

python -m pytest tests/hermes_cli/test_dashboard_param_clamps.py -q
9 passed

python -m pytest tests/test_web_server_sessiondb_eventloop.py -q
3 passed

python scripts/check-windows-footguns.py --all
No Windows footguns found (917 files scanned)

git diff --check origin/main...HEAD
clean

I also started scripts/run_tests.sh. At 28.8%, the runner had reported 7,630 passing checks and 12 failures, so I stopped the run. The failures were outside the modified files and came from missing optional SDKs, the live-system signal guard, credential-routing expectations, and a network isolation test. The focused web server tests above all pass on the rebased commit.

Build the read-only schema probe from SCHEMA_SQL so newly added columns trigger the existing writable reconciliation path. Route both cross-profile session endpoints through the same helper, including the batched Desktop sidebar endpoint.

Co-authored-by: liuhao1024 <sunsky.lau@gmail.com>
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/cli CLI entry point, hermes_cli/, setup wizard area/sessions Session lifecycle, resume, persistence, history 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
teknium1 added a commit that referenced this pull request Aug 15, 2026
…up and stop swallowing locked ALTERs

After `hermes update`, an existing state.db on an old schema made every
GET /api/sessions poll fail with sqlite3.OperationalError "no such
column: s.last_read_at" (or s.last_activity_at) until something
unrelated forced a writable open — the desktop sidebar showed "No
sessions yet" while every row sat intact on disk (#79531, #80037).

Two remaining root causes (the stale hand-written read probe was
already replaced by the SCHEMA_SQL-derived probe on main, prototyped in
draft PR #80030 by @Tilly-YL):

1. Migrations ran lazily: _init_schema/_reconcile_columns only ran on a
   writable open, typically the user's first NEW session. The dashboard
   backend now schedules one writable open of its own state.db from the
   lifespan (daemon thread, never blocks the ready-probe socket, never
   raises), so the store is brought current before the first session-
   list poll on every `hermes serve` / `hermes dashboard` / Desktop
   headless entrypoint.

2. _reconcile_columns caught sqlite3.OperationalError around every
   ALTER TABLE ADD COLUMN and logged at DEBUG. Lock contention from
   orphaned sibling backends made the ALTER fail silently — startup
   "succeeded" with a half-reconciled schema, and the open-time lock
   patience (#74478) never saw the error because it was swallowed
   inside first. Now: "duplicate column" races stay at DEBUG,
   locked/busy re-raises so _connect_and_init_with_lock_patience
   retries the whole idempotent init with jittered backoff, and any
   other failure (e.g. un-ADDable NOT NULL) logs at WARNING.

Regression tests: a store missing sessions.last_read_at is healed by
the eager startup reconcile and serves list_sessions_rich; a locked
ALTER propagates and is retried to success by the open lock patience;
duplicate-column races stay quiet; other ALTER failures warn.

Fixes #79531
Fixes #80037

Reported-by: @yenhunghuang (#79531) and @FLOW3R0111 (#80037)
Root-cause analysis: @wangyi0177-eng (stale read probe) and
@www654cc-pixel (_reconcile_columns DEBUG-swallow under lock
contention); draft PR #80030 by @Tilly-YL prototyped the probe fix.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/sessions Session lifecycle, resume, persistence, history comp/cli CLI entry point, hermes_cli/, setup wizard P2 Medium — degraded but workaround exists 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.

Cross-platform update regression: existing sessions disappear when state.db migration is skipped

3 participants