Skip to content

[BUG]: Delete session leaves data in database and on disk — race condition erases Deleted event before projector fires #30814

Closed

Description

Description

Clicking "delete session" in the UI does not always remove the session data from the database. The session row, messages, and related records can persist because of a race condition in the deletion pipeline.

Race condition: Deleted event is erased before projector processes it

File: packages/opencode/src/session/session.ts:664-665

yield* events.publish(SessionV1.Event.Deleted, { sessionID, info: session })
yield* events.remove(sessionID)

Line 664 publishes a SessionV1.Event.Deleted event to the event store. Line 665 immediately calls events.remove(sessionID) which deletes ALL events for this session from both EventTable and EventSequenceTable (packages/core/src/event.ts:445-454).

The projector at packages/core/src/session/projector.ts:248-250 listens for Deleted events asynchronously:

yield* events.project(SessionV1.Event.Deleted, (event) =>
  db.delete(SessionTable).where(eq(SessionTable.id, event.data.sessionID)).run().pipe(Effect.orDie),
)

If the projector has not processed the Deleted event before events.remove() deletes the event stream, the projector never fires. The session row and all cascade-related data remain in the database. No error is shown because the catch block at lines 666-668 silently swallows all exceptions.

Additionally, events.remove() is redundant — ON DELETE CASCADE from SessionTable (defined in packages/core/src/session/sql.ts) would clean up event rows automatically when the session row is deleted.

Note: Bugs 2-6 (tool output files, truncated output files, plan files, legacy storage, in-memory caches not cleaned up) overlap with existing issues #29694 and #16101. This issue focuses on the race condition only.

Suggested Fix

Remove the events.remove(sessionID) call at session.ts:665. The cascade delete from SessionTable already handles event cleanup. If the events.remove() is needed for a specific purpose, move it to the projector handler so it runs after the Deleted event is processed.

OpenCode version

main

Steps to reproduce

  1. Create a new session and exchange several messages
  2. Delete the session from the UI
  3. Query the SQLite database: SELECT COUNT(*) FROM session WHERE id = '<session-id>'
  4. The session row may still exist because the projector never received the Deleted event

Screenshot and/or share link

Operating System

All

Terminal

Activity

  1. github-actions commented on Jun 4, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

    The race condition in Bug 1 (events.remove() running before the projector processes the Deleted event) appears to be a new, distinct finding not covered by those issues.

  2. nexxeln commented on Jun 5, 2026

    @nexxeln
    Contributor

    synchronized event projectors run inline before events.publish(...) returns, so the session.deleted projector deletes the session row before events.remove(sessionID) runs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions