From 5ddb11b2d543d539ce702eafaf52331a570194d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Guti=C3=A9rrez?= <35082514+alezmad@users.noreply.github.com> Date: Thu, 16 Apr 2026 21:40:25 +0100 Subject: [PATCH] fix(broker): dedup presences by session_id on hello When a client reconnects with the same session_id before the 90s stale sweeper runs, the old ghost presence stays in the connections map. Result: duplicate entries in list_peers for the same Claude Code instance. Now: handleHello iterates connections for matching (meshId, sessionId), closes the old WS, deletes from map, marks disconnected in DB. One session_id = one presence, always. Co-Authored-By: Claude Opus 4.6 (1M context) --- apps/broker/src/index.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/apps/broker/src/index.ts b/apps/broker/src/index.ts index a24ba83..5d019f6 100644 --- a/apps/broker/src/index.ts +++ b/apps/broker/src/index.ts @@ -120,6 +120,7 @@ interface PeerConn { meshId: string; memberId: string; memberPubkey: string; + sessionId: string; sessionPubkey: string | null; displayName: string; cwd: string; @@ -1691,6 +1692,19 @@ async function handleHello( const initialGroups = helloHasGroups ? hello.groups! : (saved?.groups?.length ? saved.groups : (member.defaultGroups ?? [])); + // Session-id dedup: if this session_id already has an active presence, + // disconnect the ghost. Happens when a client reconnects after a + // network blip or broker restart before the 90s stale sweeper runs. + // One Claude Code instance = one session_id = one presence, always. + for (const [oldPid, oldConn] of connections) { + if (oldConn.meshId === hello.meshId && oldConn.sessionId === hello.sessionId) { + log.info("hello dedup", { old_presence: oldPid, session_id: hello.sessionId }); + try { oldConn.ws.close(1000, "session_replaced"); } catch { /* already dead */ } + connections.delete(oldPid); + void disconnectPresence(oldPid); + } + } + const presenceId = await connectPresence({ memberId: member.id, sessionId: hello.sessionId, @@ -1706,6 +1720,7 @@ async function handleHello( meshId: hello.meshId, memberId: member.id, memberPubkey: hello.pubkey, + sessionId: hello.sessionId, sessionPubkey: hello.sessionPubkey ?? null, displayName: effectiveDisplayName, cwd: hello.cwd,