Skip to content

OpenCode 2: deleted-session cleanup depends on one live event loop, which stops on the first error #745

Closed

Description

@null-axiom

Magic Context: code references at v0.49.15. OpenCode 2 only.

1. No orphan sweep on OpenCode 2

features/magic-context/message-index.ts:1016-1038: on opencode2 the orphan sweep parks itself for a day and returns "unavailable: OpenCode 2 keeps its sessions in session_v2, which this sweep does not read". The only cleanup is the live session.deleted handler (v2/hooks/context.ts:1336-1343) plus retryPendingSessionCleanups, which retries only sessions already marked there.

So any session deleted while this process isn't subscribed keeps its FTS, tags, compartments and embeddings forever: deletes from another OpenCode process or the CLI, deletes while OpenCode is closed, and a boot with storage unavailable (if (db) skips cleanup).

(When we bulk-deleted 811 subagent sessions through the running server, the event path did clean them all; this is about the cases where the event isn't seen.)

Suggestion: an OpenCode 2 sweep that diffs session_v2 ids against the session-scoped tables; V2StoreReader already reads session_v2.

2. One exception ends the event loop for the process lifetime

v2/hooks/context.ts:1300-1385: the try wraps the whole for await; the catch logs "v2 usage subscription failed" and nothing resubscribes. Calls in the session.deleted branch can throw (markSessionCleanupPending / clearSession on SQLITE_BUSY under multi-process contention, nativeFoldReplay.forget, transform.clearSessionLogState). After one throw, deletion cleanup, usage recording and revert handling stop for that process.

Suggestion: try/catch per event, and resubscribe when the stream ends or errors.

3. A subagent can be classified as primary for good

storage-meta-session.ts:112-146 reads the parent link only when the session_meta row is first created. If that lookup returns null (row not yet visible, or the read throws), a primary row is inserted and kept ("retain their served mode"), so historian eligibility, primary guidance and tool hiding stay wrong for that session. Rare, since OpenCode 2 writes parent_id first. Suggestion: retry on null instead of inserting a primary row. Related: #612 (closed).

Activity

  1. magic-alfonso commented on Oct 20, 2026

    @magic-alfonso

    Thanks, confirmed from source: on OpenCode 2 the event loop runs inside one outer try, so a throw from any handler (for example in the session.deleted branch) ends the subscription for that process, along with usage recording and revert handling. A fix is in progress: errors are caught per event and the subscription restarts when the stream ends or fails. The subagent classification on a null parent read is part of the same fix.

  2. magic-alfonso commented on Oct 22, 2026

    @magic-alfonso

    Fixed in 0.49.17. Thanks for tracing it.

    • Event errors: an error in one OpenCode 2 event handler no longer ends the plugin's event subscription. Errors are contained per event, and the subscription reconnects with backoff.
    • Subagent detection: a failed parent-session lookup no longer marks a subagent as a main session for good.
    • Deleted sessions: OpenCode 2 had no cleanup after a deleted session, so a new sweep now removes Magic Context's state for sessions OpenCode no longer has. It deletes in small slices so it never holds the database lock for long. It skips anything indexed in the last 24 hours, and refuses to run if OpenCode's session table reads empty.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions