Repository navigation
Conversation
sendSSEToStreams read the session, bumped eventId on that copy and then called addMessage with the result. Broadcast delivery does not await it, so overlapping deliveries read the same counter and every event of a burst went out as `id: 1`. That made Last-Event-ID resumption ambiguous, and on Redis the duplicate XADD ids were rejected, losing history. SessionStore.addMessage(sessionId, message) now allocates the next id itself and returns it (null when the session is gone): - MemorySessionStore increments the counter on the stored session object synchronously, before any await. - RedisSessionStore runs one Lua script: HINCRBY the counter, XADD under that id, XTRIM, update lastEventId/lastActivity and the TTL. Doing the allocation and the append in one step also keeps stream ids ascending, and the script no longer recreates a session hash that has expired. Both call sites in routes/mcp.ts use the returned id. Fixes #317 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LmhsFjk8W96X2PvmShmDm9
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
sendSSEToStreamspicked the next SSE event id by reading the session, bumpingeventIdon that copy, and then passing the result toaddMessage. Broadcast delivery doesn't await this, so deliveries that overlap all read the same counter. FourmcpBroadcastNotificationcalls in aPromise.allsentid: 1four times, on both the memory and the Redis backend. That makes Last-Event-ID resumption ambiguous. On Redis it also loses history, because XADD rejects the duplicate stream ids.Fix
The store now allocates the event id.
SessionStore.addMessage(sessionId, message)assigns the next id, records the message under it, and returns the id. It returnsnullwhen the session no longer exists.HINCRBY eventId, thenXADDunder that id, thenXTRIM, then updateslastEventId/lastActivityand refreshes the TTL. Because the id is allocated and appended in one step, stream ids also stay ascending. If allocation and append were separate calls, XADD could still reject a lower id that arrives late. The script returns early when the session hash is gone, so an expired session is no longer recreated without a TTL. The old pipeline did recreate it.addMessagechanges signature: theeventIdargument is removed and the method now returns the id.SessionStoreis exported as a type, but the plugin only ever builds its own stores, so no option for plugging in a custom store breaks.Tests
test/sse-event-id-concurrency.test.ts(new) runs the repro from the issue against both backends. It opens a session and a GET stream, sends 4 concurrent broadcasts, and asserts the ids are unique and equal to 1..4. It then reconnects withLast-Event-ID: 1and asserts that 2, 3 and 4 are replayed. Before the fix it failed with["1","1","1","1"]on both backends.test/memory-session-store.test.tsandtest/redis-session-store.test.ts, concurrentaddMessagecalls get distinct ids and none are lost from history, andaddMessageon a missing session returnsnull. The Redis test also checks that no keys get created.npm run lint,npm run typecheckandnpm run test(772 tests) all pass.Fixes #317
🤖 Generated with Claude Code
https://claude.ai/code/session_01LmhsFjk8W96X2PvmShmDm9