Skip to content

Fix UNIQUE constraint on events.sequence during concurrent tool calls#139

Open
alexgorbatchev wants to merge 1 commit intobenjitaylor:mainfrom
alexgorbatchev:fix/concurrent-sequence-collision-issue-138
Open

Fix UNIQUE constraint on events.sequence during concurrent tool calls#139
alexgorbatchev wants to merge 1 commit intobenjitaylor:mainfrom
alexgorbatchev:fix/concurrent-sequence-collision-issue-138

Conversation

@alexgorbatchev
Copy link

Summary

  • Restructures event persistence to assign sequence numbers atomically with the SQLite insert, then notify SSE subscribers — preventing duplicate sequences when concurrent MCP tool calls hit the HTTP server
  • Adds retry loop (up to 5 attempts) on UNIQUE constraint failed: events.sequence to gracefully handle any remaining edge cases
  • Adds nextSequence() and notify() methods to EventBus so SQLite-backed stores can persist before distributing

Changes

  • events.ts: Split emit() into nextSequence() + notify() building blocks. emit() still works as before for non-persisted events (in-memory store, action.requested).
  • sqlite.ts: Replace eventBus.emit() → persistEvent() pattern with emitAndPersist() which: assigns sequence → inserts into DB → notifies subscribers. Same for tenant store.

How it fixes the bug

Previously:

  1. eventBus.emit() — increments globalSequence, notifies SSE subscribers
  2. persistEvent() — inserts into SQLite

Now:

  1. emitAndPersist() — increments sequence, inserts into SQLite, then notifies SSE subscribers
  2. On UNIQUE violation, retries with a fresh sequence number

This ensures the sequence number in the database always matches what SSE subscribers receive, and concurrent inserts can't produce duplicates.

Fixes #138

Test plan

  • Build succeeds (pnpm build in mcp/)
  • Start server, create annotations, dismiss/resolve multiple concurrently from Claude Code
  • Verify no UNIQUE constraint failed errors
  • Verify SSE event IDs match database sequences (reconnect with Last-Event-ID works correctly)

🤖 Generated with Claude Code

…P tool calls

When multiple MCP tool calls (e.g. dismiss, resolve) are invoked concurrently,
the sequence counter could collide because emit() assigned the sequence and
notified SSE subscribers before persisting to SQLite. This restructures the
flow to persist first (with retry on UNIQUE violation), then notify subscribers,
ensuring SSE clients always see the correct, persisted sequence number.

Fixes benjitaylor#138

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@vercel
Copy link

vercel bot commented Mar 10, 2026

@alexgorbatchev is attempting to deploy a commit to the Benji Taylor's Projects Team on Vercel.

A member of the Team first needs to authorize it.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MCP: UNIQUE constraint failed on events.sequence during concurrent tool calls

1 participant