You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Orphan sweep covers message_history_index only — the other 23 session-scoped tables leak when sessions are deleted out-of-band #346
clearSession() correctly deletes from 24 session-scoped tables, but it is event-driven (session.deleted) and therefore blind to sessions removed from opencode.db by any path that doesn't emit the event. sweepOrphanedOpenCodeMessageIndexes exists as the backstop for exactly that case — but it reaps one table:
DELETE FROM message_history_index
So 23 of the 24 have no backstop. Measured on my store after an out-of-band prune: 1,992 of 6,443 session_meta rows orphaned (31%).
Filing because you clearly consider this class real enough to have built the sweep and its bookkeeping table. This is "the half that wasn't covered" plus a trap worth knowing before extending it.
Evidence
Cross-DB join against opencode.db:
session_meta rows total 6,443
orphaned (no session) 1,992 ← 31%
live 4,450
pi (see below) 1
Newest orphan's last_response_time was ~a week old, consistent with a one-shot prune rather than an ongoing leak. Cause on my box: an offline event-store prune deleted session rows directly without emitting session.deleted, so the event-driven cleanup never fired. Normal deletion is handled correctly — I checked clearSession() before assuming otherwise, and session_meta is in its delete list.
I reaped mine manually. Only ~2.0 MB, so this is hygiene rather than a space problem — the reason to care is that the rows are invisible and accumulate silently.
The trap, if you extend the sweep
This is the part I'd most want a future implementer to have. The obvious query —
DELETEFROM session_meta WHERE session_id NOT IN (SELECT id FROMoc.session)
— destroys Pi metadata. Pi sessions are legitimately absent from opencode.db by design. I had exactly one such row and nearly took it; the count is small enough that nobody would notice until Pi behaved oddly.
Any sweep of this class must scope on harness = 'opencode' first. The existing session_meta table has that column, so it's available.
Worth noting the asymmetry it creates: session_metacan be swept safely because it carries harness. Tables that don't carry a harness discriminator can't be, which may constrain how far the sweep can extend. (Related: I raised the same missing-discriminator point about the new memory_evidence table in #340's review.)
Verification helper
The arithmetic should close, which is a cheap correctness check for any implementation:
On mine: 1,992 + 1 + 4,450 = 6,443 ✓. If it doesn't close, the predicate is wrong.
Scoping, honestly
The trigger — out-of-band session deletion — is not something a typical user hits; the event path covers ordinary deletion. So this is arguably low priority. Two reasons I filed it anyway:
The infrastructure already exists (message_history_orphan_sweep bookkeeping, dream-timer tick), so extending it to session_meta is small.
The failure is silent. Nothing surfaces orphaned rows, and I only found them because I was chasing an unrelated stale row.
Happy to send a PR for a harness-scoped session_meta sweep reusing the existing bookkeeping pattern, if you want it. Equally happy to have this closed as working-as-intended — the event path really is the right primary mechanism, and I'd rather ask than assume the sweep's single-table scope was an oversight.
Fixed on master, ships next release. The sweep and clearSession() now consume one shared session-table list, so all tables are swept and a future addition can't silently miss — a schema-derived lockstep test fails the build if the list drifts from reality. Age gate and batching preserved. Your 31% measurement made the priority call easy — thank you. (Pi gets its own sweep as a follow-up; it lacks a terminal deletion event to hook.)
Activity
fix(#346): orphan sweep reaps ALL session-scoped tables via the same …
alfonso-magic-context commentedon Aug 20, 2026
Fixed on master, ships next release. The sweep and
clearSession()now consume one shared session-table list, so all tables are swept and a future addition can't silently miss — a schema-derived lockstep test fails the build if the list drifts from reality. Age gate and batching preserved. Your 31% measurement made the priority call easy — thank you. (Pi gets its own sweep as a follow-up; it lacks a terminal deletion event to hook.)