{"record":{"id":"48ba00db81a671c5","repo":"block/buzz","slug":"relay-disconnected-for-community-switch","errorCode":null,"errorMessage":"Relay disconnected for community switch.","messagePattern":"Relay disconnected for community switch\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"desktop/src/shared/api/relayEventPublisher.ts","lineNumber":26,"sourceCode":"  ownership: () => number;\n  pendingEvents: Map<string, PendingEvent>;\n  send: (payload: unknown[], generation: number) => Promise<void>;\n  reconnect: () => Promise<number>;\n  normalizeError: (error: unknown, fallback: string) => Error;\n  recoverSocketFailure: (error: unknown, fallback: string) => Error;\n};\n\n/** Publish once, with one reconnect retry, without crossing session ownership. */\nexport async function publishSessionEvent(\n  session: PublishSession,\n  event: RelayEvent,\n  timeoutMessage: string,\n  sendErrorMessage: string,\n): Promise<RelayEvent> {\n  const publishOwnership = session.ownership();\n  await waitForRateLimit();\n  if (publishOwnership !== session.ownership()) {\n    throw new Error(\"Relay disconnected for community switch.\");\n  }\n  const publishGeneration = session.generation();\n\n  return new Promise<RelayEvent>((resolve, reject) => {\n    const timeout = window.setTimeout(() => {\n      session.pendingEvents.delete(event.id);\n      reject(new Error(timeoutMessage));\n    }, PUBLISH_TIMEOUT_MS);\n    const pendingEvent = { event, resolve, reject, timeout };\n    session.pendingEvents.set(event.id, pendingEvent);\n\n    void session\n      .send([\"EVENT\", event], publishGeneration)\n      .catch(async (error) => {\n        // A disconnect may already have rejected this operation while the send\n        // was in flight. Its late failure must not reset the replacement session.\n        if (\n          publishOwnership !== session.ownership() ||","sourceCodeStart":8,"sourceCodeEnd":44,"githubUrl":"https://github.com/block/buzz/blob/dad5a33865fc81a2e55b3b60746632f615ec1e3a/desktop/src/shared/api/relayEventPublisher.ts#L8-L44","documentation":"publishSessionEvent snapshots the session's publish ownership before the rate-limit wait and throws if ownership changed — the relay session was disconnected/replaced for a community switch while this publish was pending. It guarantees a publish never proceeds under a session that no longer owns the connection.","triggerScenarios":"Calling publishEvent() (via publishSessionEvent) while a community switch is disconnecting/replacing the session; waitForRateLimit() yields and, on resume, the ownership token no longer matches.","commonSituations":"User switches communities while a message/media event is publishing; logout during publish; rapid community switching with queued events.","solutions":["Delay or cancel publishes during a community switch and re-dispatch on the new session.","Catch and retry after the switch completes (fresh ownership/generation).","Gate send buttons on community-switch-in-progress state.","Check for multiple components publishing through a session being torn down."],"exampleFix":"// before\nawait publishSessionEvent(session, event, ...);\n// after\ntry { await publishSessionEvent(session, event, ...); }\ncatch (e) {\n  if (isSuperseded(e)) await publishSessionEvent(session, event, ...); // retry on new session\n  else throw e;\n}","handlingStrategy":"try-catch","validationCode":"if (session.switchInProgress()) await session.switchComplete; // hold publishes until stable\n","typeGuard":"function isSupersededError(e: unknown): boolean { return e instanceof Error && e.message.includes('superseded by a session change'); }\n","tryCatchPattern":"try { await publishEvent(ev); } catch (e) { if (isSupersededError(e) || e.message.includes('community switch')) await republishOnCurrentSession(ev); else throw e; }\n","preventionTips":["Disable send actions while a community switch is in progress","Re-dispatch queued events after the new session is ready","Never reuse a session reference after teardown"],"tags":["relay","race-condition","community-switch"],"backgroundTag":"stale-connection-generation","analyzedSha":"dad5a33865fc81a2e55b3b60746632f615ec1e3a","analyzedAt":"2026-09-05T18:13:50.666Z","contentChangedAt":"2026-09-05T18:13:50.666Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}