Skip to content

fix(state): repair duplicate session titles without data loss on startup (#65602) - #65636

Closed
Tranquil-Flow wants to merge 1 commit into
NousResearch:mainfrom
Tranquil-Flow:fix/65602-session-title-index
Closed

fix(state): repair duplicate session titles without data loss on startup (#65602)#65636
Tranquil-Flow wants to merge 1 commit into
NousResearch:mainfrom
Tranquil-Flow:fix/65602-session-title-index

Conversation

@Tranquil-Flow

Copy link
Copy Markdown
Contributor

Summary

SessionDB._init_schema() creates a UNIQUE partial index on sessions.title but only catches sqlite3.OperationalError. Existing databases that contain duplicate non-null titles (from imports, context-compression continuations, or concurrent creation) raise sqlite3.IntegrityError instead, which is uncaught — crashing Dashboard API calls that open the session database (/api/sessions/{id}/messages).

Root cause

Commit 60b6abefd (session naming with unique titles) added the CREATE UNIQUE INDEX guarded only by an OperationalError handler ("index already exists"). It does not handle the case where the index does not yet exist but the table already holds duplicate titles — which throws IntegrityError.

Fix

Catch sqlite3.IntegrityError and repair the duplicates without deleting any data:

  • Of each duplicate-title group, retain the title on the newest inserted session (newer.rowid > older.rowid) and set title = NULL only on the older conflicting aliases.
  • Log how many titles were cleared.
  • Retry the unique-index creation.
  • The existing OperationalError ("index already exists") handler is preserved.

Titles are optional aliases, so clearing an older alias does not lose the session or its messages.

Why not delete duplicate rows

The alternative (deleting duplicate sessions) causes severe transcript data loss. Competing PR #65628 uses:

DELETE FROM sessions WHERE rowid NOT IN (
  SELECT MAX(rowid) FROM sessions
  WHERE title IS NOT NULL GROUP BY title
)

That subquery only selects MAX(rowid) within non-null-title groups, so every untitled session (the majority of normal sessions) is deleted, along with all but the newest duplicate. This fix preserves every row.

Tests

New TestSessionTitleIndexRepair class in tests/test_hermes_state.py (3 production-path tests against a real SQLite database):

Test What it asserts
test_duplicate_titles_are_repaired_without_deleting_sessions All sessions + messages preserved; newest keeps the alias, older duplicate cleared; unique index exists after repair
test_repaired_index_rejects_future_duplicate_title After repair, reusing the title via set_session_title is rejected (ValueError)
test_clean_legacy_database_keeps_existing_titles A clean database (no duplicates) initializes unchanged

RED proof (on upstream/main before the production change): 2 of 3 tests fail with sqlite3.IntegrityError: UNIQUE constraint failed: sessions.title at _init_schema; the clean-database test passes.

GREEN after fix: focused class 3 passed; full tests/test_hermes_state.py 369 passed. ruff check clean.

Fixes #65602


Auto-published by Moonsong via Path B automated pipeline.

@alt-glitch alt-glitch added type/bug Something isn't working comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/dashboard Web dashboard / control panel UI (dashboard/, landing) P2 Medium — degraded but workaround exists needs-decision Awaiting maintainer decision before any implementation sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state labels Jul 16, 2026
teknium1 added a commit that referenced this pull request Jul 16, 2026
… open

Follow-up to the salvaged #65636: if the dedup UPDATE or the retried
CREATE INDEX raises, log and continue — the unique title index is an
optimization and must not block SessionDB initialization.
@teknium1

Copy link
Copy Markdown
Contributor

Merged via PR #65689 — your commit was cherry-picked onto current main with your authorship preserved in git log (rebase-merge). We added one small follow-up on top wrapping the repair itself in a guard so index creation can never abort DB open. The no-data-loss UPDATE approach and the RED-proven tests were exactly right — thanks!

@teknium1 teknium1 closed this Jul 16, 2026
Gravezzz pushed a commit to Gravezzz/hermes-agent that referenced this pull request Jul 21, 2026
… open

Follow-up to the salvaged NousResearch#65636: if the dedup UPDATE or the retried
CREATE INDEX raises, log and continue — the unique title index is an
optimization and must not block SessionDB initialization.
randlee pushed a commit to randlee/hermes-agent that referenced this pull request Aug 11, 2026
… open

Follow-up to the salvaged NousResearch#65636: if the dedup UPDATE or the retried
CREATE INDEX raises, log and continue — the unique title index is an
optimization and must not block SessionDB initialization.
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 comp/dashboard Web dashboard / control panel UI (dashboard/, landing) needs-decision Awaiting maintainer decision before any implementation P2 Medium — degraded but workaround exists 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.

fix: catch IntegrityError on sessions.title UNIQUE index creation

3 participants