Skip to content

Bug: stale event sequence permanently rejects new session events #51411

Closed as not planned

Description

@d4n-sec

Description

A durable aggregate can become permanently unwritable when event_sequence.seq falls behind rows already present in event. The next local publish derives seq = event_sequence.seq + 1, updates the sequence row, then collides with the existing (aggregate_id, seq) unique index. The transaction rolls back, so every later prompt repeats the same collision.

The event log itself remains intact; the failure is the stale sequence cursor. New publishes should recover inside the existing immediate transaction by deriving the local high-water mark from both tables, then write the next contiguous sequence without changing owner-claim behavior.

Plugin developers commonly run and compare v1 and v2 while supporting both host generations. That is a normal development workflow, not an unsupported user mistake. If only OPENCODE_CONFIG_DIR is isolated while the default data DB remains shared, cross-generation startup can leave an active v1 session damaged (see #42260). This defensive recovery is necessary for a legacy database already left in that state; it does not replace proper v1/v2 data isolation.

Plugins

None

OpenCode version

1.18.32 (observed); latest dev inspected for the fix

Steps to reproduce

  1. Create an aggregate and durable events through any normal OpenCode path.
  2. In a stopped, disposable SQLite fixture, lower only that aggregate's event_sequence.seq below the highest event.seq; do not alter the event rows.
  3. Start OpenCode and publish another durable event for the aggregate.
  4. Observe UNIQUE constraint failed: event.aggregate_id, event.seq; retries use the same stale sequence and the session remains unusable.

This can be left behind by historical cross-version/shared-database migration scenarios. Related context: #42260, #42444, #42389, and the template-closed report #51316.

Operating System

macOS 26 / Apple Silicon

Terminal

iTerm2

Activity

  1. github-actions commented on Sep 25, 2026

    @github-actions
    Contributor

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

    • #51316: reports the same UNIQUE constraint failed: event.aggregate_id, event.seq error and a permanently unusable session after retries. It attributes the collision to concurrent sequence assignment, while this issue describes a stale event_sequence cursor.
  2. d4n-sec commented on Sep 25, 2026

    @d4n-sec
    Author

    Thanks for the duplicate check. #51316 and this report share the visible failure (UNIQUE constraint failed: event.aggregate_id, event.seq) and the permanent retry behavior, but they do not establish the same initial cause. #51316 attributes the first collision to concurrent assignment. This report deterministically isolates a durable post-condition: an aggregate already has an event head higher than its persisted event_sequence cursor. A later publish derives an already-used sequence; rollback leaves that cursor unchanged, so every retry collides again.\n\nThat durable mismatch could plausibly be left behind after a concurrent collision, but it can also be introduced by historical shared-database/cross-version scenarios. PR #51413 reconciles the cursor with the actual event head in the same transaction before allocating the next sequence. It does not replace correct concurrent allocation, but it makes either persisted stale-cursor state recoverable.\n\nI will defer to maintainers on whether #51411 should be treated as a duplicate, related report, or the recovery-focused follow-up to #51316.

  3. ImBIOS commented on Oct 4, 2026

    @ImBIOS
    Contributor

    Hit this in the wild without any manual fixture edits — the desync developed during normal streaming.

    Evidence from the live DB (binary v2.0.22, Linux x64, WAL mode, 9.6G shared opencode.db):

    • One session: 687 contiguous events (seq 42–728), but event_sequence.seq stuck at 691. Next publish tried seq 692 → UNIQUE constraint failed: event.aggregate_id, event.seq → whole drain aborted (prompt_async failed), session permanently unwritable; every retry collides on the same seq.
    • Only 1 of 1089 aggregates in event_sequence was affected.
    • Reverse direction also observed: after the crash, a failed retry drain advanced event_sequence 728 → 1118 with zero new events committed (max stayed 728). So the counter is decoupled from committed events in both directions, not just behind.
    • Topology note: 3 server processes share this DB (two T3-managed opencode serve on 127.0.0.1, the second spawned right after the crash, plus a pre-existing opencode serve --service). Possibly related to session: two server processes sharing one opencode.db allocate session_message.seq independently - UNIQUE(seq) collisions fail sessions #53146.

    Workaround used: manual UPDATE event_sequence SET seq=(SELECT max(seq)...). This supports the in-transaction high-water-mark recovery suggested in the issue description — deriving the next seq from both tables would have self-healed both directions here.

  4. opencodejr commented on Oct 28, 2026

    @opencodejr

    Thanks for the precise report and steps.

    I couldn't reproduce this on v2 (@opencode/cli@2.2.0, the latest release). Here's what I did:

    1. Sent a prompt in a new session. event_sequence.seq for the session was 6.
    2. Lowered only that cursor by 2, leaving everything else alone.
    3. Sent two more prompts. Both went through with no UNIQUE constraint failed: event.aggregate_id, event.seq error, and the cursor carried on from 4.

    On v2 a stale cursor can't cause this collision. The local CLI and service never write rows to the event table: the write is behind a persist option that defaults to off in packages/core/src/bus.ts, and only the workerd server turns it on. So there is nothing for event_sequence.seq + 1 to collide with. The same logic is in v2.0.32, v2.1.0 and current dev (86ce19d).

    The failure you describe comes from v1 (opencode-ai 1.x, including 1.18.32), which does write event rows. The v2 reports of the same error came from a v1 or desktop 1.x process sharing one opencode.db with v2, as in #42260. Current v2 keeps its data in a separate opencode-next.db. v1 only receives fixes for critical bugs, so a recovery step there is unlikely.

    Related issues:

    If you can trigger this on v2 alone, with no v1 or desktop 1.x process using the same data directory, please reply with your opencode --version, the database path in use and the steps. We'll look again.

  5. opencodejr commented on Oct 31, 2026

    @opencodejr

    We haven't heard back, so I'm closing this as not planned.

    To recap: I couldn't reproduce this on v2 (@opencode/cli@2.2.0). After I lowered a session's event_sequence.seq, the next prompts still went through, because the local v2 app doesn't write rows to the event table. The collision you describe comes from v1 (1.18.32) writing to a database shared with v2. v1 only receives fixes for critical bugs, and current v2 keeps its data in a separate opencode-next.db. Related: #56460, #54464, #52395, #42260.

    If this happens on v2 alone, with no v1 or desktop 1.x process using the same data directory, please open a new issue with your opencode --version, the database path and the steps, and we'll look again. Thanks for the detailed report.

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