Skip to content

fix(gateway): retry failed transcript appends and rebuild corrupted FTS - #66256

Merged
kshitijk4poor merged 2 commits into
NousResearch:mainfrom
kshitijk4poor:salvage/65637-transcript-append-retry
Jul 17, 2026
Merged

kshitijk4poor merged 2 commits into
NousResearch:mainfrom
kshitijk4poor:salvage/65637-transcript-append-retry

Conversation

@kshitijk4poor

Copy link
Copy Markdown
Contributor

Summary

Transcript DB append failures (including SQLite FTS corruption) no longer silently drop messages — failed appends are queued in-memory and retried in order on the next append, with FTS rebuild attempted once per store on corruption errors.

Cherry-picked from #65637 by @MaartenDMT, with five hardening fixes on top.

Changes

  • gateway/session.py: append_to_transcript queues failed messages per session and retries them in order on subsequent appends. FTS corruption errors trigger SessionDB.rebuild_fts() once per store. Log level escalated from debug to warning with failure/pending counters.
  • hermes_state.py: Added SessionDB.rebuild_fts() method (mirrors optimize_fts() pattern — acquires _lock, iterates _FTS_TABLES, calls _fts_table_exists, executes FTS5 rebuild command).
  • tests/gateway/test_session.py: 5 new tests covering retry+rebuild flow, dirty-clear on rewrite/rewind, FTS matcher false positives, pending cap enforcement.

Hardening fixes (on top of contributor's commit)

  1. Clear _dirty_transcripts in rewrite_transcript + rewind_session — stale pending messages were re-inserted after /retry, /undo, /compress replaced the transcript.
  2. Narrow _is_fts_corruption_error — bare "fts" substring matched "shifts", "gifts", etc. Now matches specific SQLite error strings.
  3. Move DB write outside _transcript_retry_lock — holding the lock during writes serialized all sessions' transcript appends and blocked during FTS rebuild. Lock now guards only the pending queue.
  4. Push rebuild_fts() into SessionDB — SessionStore was reaching into _conn/_lock private attrs. SessionDB.rebuild_fts() follows the existing optimize_fts() pattern.
  5. Cap pending per session at 200 — prevents unbounded memory growth when DB is persistently broken. Oldest messages dropped with warning.

Validation

Before After
Failed append behavior Silent debug log, message lost Queued + retried, warning logged
FTS corruption recovery None FTS5 rebuild attempted once per store
Cross-session lock contention N/A DB writes outside lock (E2E: 2×100ms appends = 0.11s)
Pending memory bound Unbounded Capped at 200 per session
rewrite/rewind stale pending Re-inserted Cleared

E2E tests: 6/6 passed (real SessionDB, real FK constraint, real FTS rebuild, concurrency verified).
Unit tests: 9/9 passed (5 new + 4 existing in TestGatewaySessionDbRecovery). 3 pre-existing failures on origin/main confirmed unrelated.

Closes #65637

@alt-glitch alt-glitch added type/bug Something isn't working P1 High — major feature broken, no workaround comp/gateway Gateway runner, session dispatch, delivery sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state labels Jul 17, 2026

@tonydwb tonydwb left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Summary

Verdict: Approved (LGTM)

Overview

Retries failed transcript appends and rebuilds corrupted FTS in gateway. +320/0.

Security

  • No hardcoded secrets or credentials

Code Quality

  • Clean retry and recovery logic
  • Proper FTS rebuild mechanism

Looks Good

  • Well-scoped resilience fix

Reviewed by Hermes Agent

MaartenDMT and others added 2 commits July 17, 2026 18:48
Queue failed session DB appends so disk order cannot silently lag memory.\nRebuild corrupt FTS indexes once and surface repeated failures as warnings.
Follow-up fixes for salvaged PR NousResearch#65637:

1. Clear _dirty_transcripts in rewrite_transcript + rewind_session —
   stale pending messages were re-inserted after /retry, /undo, /compress.

2. Narrow _is_fts_corruption_error to specific SQLite error strings —
   bare 'fts' substring matched 'shifts', 'gifts', etc.

3. Move DB write outside _transcript_retry_lock — holding the lock
   during writes serialized all sessions' transcript appends and blocked
   during FTS rebuild. Now the lock guards only the pending queue.

4. Push rebuild_fts() into SessionDB — SessionStore was reaching into
   _conn/_lock private attrs. SessionDB.rebuild_fts() follows the same
   pattern as optimize_fts().

5. Cap pending per session at 200 — prevents unbounded memory growth
   when DB is persistently broken. Oldest messages dropped with warning.

Added 4 new tests: dirty-clear on rewrite/rewind, FTS matcher false
positives, pending cap enforcement.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery P1 High — major feature broken, no workaround 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.

4 participants