fix(memory): backfill sessions row in hooks; add SQLite busy_timeout (#49)#51
Open
fazleelahhee wants to merge 2 commits intomainfrom
Open
fix(memory): backfill sessions row in hooks; add SQLite busy_timeout (#49)#51fazleelahhee wants to merge 2 commits intomainfrom
fazleelahhee wants to merge 2 commits intomainfrom
Conversation
…49) The lifecycle invariant — \"SessionStart fires before any other hook for the same session_id\" — breaks in real deployments (cce serve started mid-Claude-Code-session, resumed session_ids that don't re-fire SessionStart, dropped POST). Each subsequent UserPromptSubmit and PostToolUse then trips the FK on sessions(id) and crashes the handler: sqlite3.IntegrityError: FOREIGN KEY constraint failed Make each handler self-healing: a new _ensure_session() helper does an INSERT OR IGNORE INTO sessions before any insert that depends on the parent row. The placeholder uses app[\"project_name\"] and the current epoch; INSERT OR IGNORE means a real SessionStart arriving later does not clobber the existing row's started_at. Applied to UserPromptSubmit, PostToolUse, Stop, and SessionEnd. (Stop writes only to pending_compressions which has no FK; SessionEnd's UPDATE is a no-op on a missing row. Adding the backfill anyway keeps the row queryable in dashboard listings.) Separately, the same issue report includes 'sqlite3.OperationalError: database is locked' from the auto_prune background task contending with hot-path inserts. WAL was already enabled but no busy_timeout was set, so contention returned SQLITE_BUSY immediately. Add PRAGMA busy_timeout = 5000 so writers retry for up to 5s instead of crashing on the first collision. Tests: - UserPromptSubmit / PostToolUse without prior SessionStart succeed and create the session row - Real SessionStart arriving after backfill keeps the original started_at_epoch (INSERT OR IGNORE semantics) - PRAGMA busy_timeout >= 1000ms on every new connection Closes #49.
Contributor
There was a problem hiding this comment.
Pull request overview
Fixes two reliability issues in the memory hook write path: (1) FK failures when hook events arrive before SessionStart, and (2) SQLite lock contention causing immediate SQLITE_BUSY errors instead of waiting/retrying.
Changes:
- Add
_ensure_session()to backfill a placeholdersessionsrow before FK-dependent inserts in multiple hook handlers. - Configure SQLite connections with
PRAGMA busy_timeout = 5000to reduce “database is locked” failures under contention. - Add regression tests covering orphan hook events and verifying
busy_timeoutis set.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/context_engine/memory/hooks.py |
Adds _ensure_session() and calls it from multiple hook handlers to prevent FK failures when SessionStart hasn’t been seen. |
src/context_engine/memory/db.py |
Sets PRAGMA busy_timeout = 5000 in connect() to handle write contention more gracefully. |
tests/memory/test_hooks.py |
Adds regression tests for orphan UserPromptSubmit/PostToolUse and late SessionStart behavior. |
tests/memory/test_db.py |
Adds a test that asserts busy_timeout is configured on new connections. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+60
to
+62
| handler proceed; the row will be reconciled if a real SessionStart | ||
| arrives later (INSERT OR IGNORE), and the session_id remains queryable | ||
| in the meantime. |
Comment on lines
+352
to
+353
| assert prompts == [{"prompt_number": 1, "prompt_text": "hi"}] \ | ||
| if isinstance(prompts[0], dict) else len(prompts) == 1 |
rajkumarsakthivel
approved these changes
May 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #49.
Two related bugs in the memory-hook write path:
FOREIGN KEY constraint failedonUserPromptSubmit/PostToolUsewhen those hooks arrive beforeSessionStartfor a givensession_id(cce serve started mid-Claude-Code-session, resumed session_id without re-fired SessionStart, dropped POST). Make each handler self-healing via a new_ensure_session()helper that doesINSERT OR IGNORE INTO sessionsbefore the FK-dependent inserts.INSERT OR IGNOREmeans a realSessionStartarriving later does not clobber the placeholder'sstarted_at_epoch.sqlite3.OperationalError: database is lockedinauto_prune_loop— WAL was already on but nobusy_timeout, so contention with hot-path inserts crashed immediately instead of retrying. SetPRAGMA busy_timeout = 5000inconnect().Test plan
tests/memory/test_hooks.py— new tests cover:UserPromptSubmitwithout priorSessionStartsucceeds and backfills the session rowPostToolUsewithout priorSessionStartsucceeds and backfills the session rowSessionStartarriving after backfill does not overwrite the placeholder rowtests/memory/test_db.py— pinPRAGMA busy_timeout >= 1000mson every new connection339 passedruff check)Files
src/context_engine/memory/hooks.py—_ensure_session()helper + calls in 4 handlerssrc/context_engine/memory/db.py—PRAGMA busy_timeout = 5000inconnect()tests/memory/test_hooks.py— 3 new teststests/memory/test_db.py— 1 new test